summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.zuul.yaml145
-rw-r--r--README.rst2
-rw-r--r--doc/source/admin/interoperable-image-import.rst10
-rw-r--r--doc/source/admin/multistores.rst2
-rw-r--r--doc/source/admin/useful-image-properties.rst8
-rw-r--r--doc/source/cli/glancestatus.rst95
-rw-r--r--doc/source/contributor/blueprints.rst2
-rw-r--r--doc/source/contributor/minor-code-changes.rst12
-rw-r--r--doc/source/user/glancemetadefcatalogapi.rst4
-rw-r--r--etc/glance-image-import.conf.sample8
-rw-r--r--etc/oslo-config-generator/glance-api.conf1
-rw-r--r--etc/rootwrap.conf27
-rw-r--r--glance/api/v2/images.py112
-rw-r--r--glance/async_/__init__.py (renamed from glance/async/__init__.py)0
-rw-r--r--glance/async_/flows/__init__.py (renamed from glance/async/flows/__init__.py)0
-rw-r--r--glance/async_/flows/_internal_plugins/__init__.py (renamed from glance/async/flows/_internal_plugins/__init__.py)0
-rw-r--r--glance/async_/flows/_internal_plugins/web_download.py (renamed from glance/async/flows/_internal_plugins/web_download.py)0
-rw-r--r--glance/async_/flows/api_image_import.py (renamed from glance/async/flows/api_image_import.py)4
-rw-r--r--glance/async_/flows/base_import.py (renamed from glance/async/flows/base_import.py)2
-rw-r--r--glance/async_/flows/convert.py (renamed from glance/async/flows/convert.py)0
-rw-r--r--glance/async_/flows/introspect.py (renamed from glance/async/flows/introspect.py)2
-rw-r--r--glance/async_/flows/ovf_process.py (renamed from glance/async/flows/ovf_process.py)0
-rw-r--r--glance/async_/flows/plugins/__init__.py (renamed from glance/async/flows/plugins/__init__.py)0
-rw-r--r--glance/async_/flows/plugins/image_conversion.py (renamed from glance/async/flows/plugins/image_conversion.py)8
-rw-r--r--glance/async_/flows/plugins/inject_image_metadata.py (renamed from glance/async/flows/plugins/inject_image_metadata.py)0
-rw-r--r--glance/async_/flows/plugins/no_op.py (renamed from glance/async/flows/plugins/no_op.py)0
-rw-r--r--glance/async_/flows/plugins/plugin_opts.py (renamed from glance/async/flows/plugins/plugin_opts.py)8
-rw-r--r--glance/async_/taskflow_executor.py (renamed from glance/async/taskflow_executor.py)4
-rw-r--r--glance/async_/utils.py (renamed from glance/async/utils.py)0
-rw-r--r--glance/cmd/__init__.py18
-rw-r--r--glance/cmd/status.py28
-rw-r--r--glance/common/config.py26
-rw-r--r--glance/common/timeutils.py1
-rw-r--r--glance/common/utils.py2
-rw-r--r--glance/common/wsgi.py33
-rw-r--r--glance/db/migration.py2
-rw-r--r--glance/db/sqlalchemy/api.py29
-rw-r--r--glance/domain/__init__.py10
-rw-r--r--glance/locale/en_GB/LC_MESSAGES/glance.po49
-rw-r--r--glance/notifier.py2
-rw-r--r--glance/opts.py20
-rw-r--r--glance/quota/__init__.py8
-rw-r--r--glance/tests/functional/__init__.py4
-rw-r--r--glance/tests/functional/db/base.py24
-rw-r--r--glance/tests/functional/v2/test_images.py31
-rw-r--r--glance/tests/stubs.py40
-rw-r--r--glance/tests/unit/api/test_common.py2
-rw-r--r--glance/tests/unit/async_/__init__.py (renamed from glance/tests/unit/async/__init__.py)0
-rw-r--r--glance/tests/unit/async_/flows/__init__.py (renamed from glance/tests/unit/async/flows/__init__.py)0
-rw-r--r--glance/tests/unit/async_/flows/plugins/__init__.py (renamed from glance/tests/unit/async/flows/plugins/__init__.py)0
-rw-r--r--glance/tests/unit/async_/flows/plugins/test_image_conversion.py124
-rw-r--r--glance/tests/unit/async_/flows/plugins/test_inject_image_metadata.py (renamed from glance/tests/unit/async/flows/plugins/test_inject_image_metadata.py)2
-rw-r--r--glance/tests/unit/async_/flows/test_api_image_import.py (renamed from glance/tests/unit/async/flows/test_api_image_import.py)4
-rw-r--r--glance/tests/unit/async_/flows/test_convert.py (renamed from glance/tests/unit/async/flows/test_convert.py)4
-rw-r--r--glance/tests/unit/async_/flows/test_import.py (renamed from glance/tests/unit/async/flows/test_import.py)6
-rw-r--r--glance/tests/unit/async_/flows/test_introspect.py (renamed from glance/tests/unit/async/flows/test_introspect.py)4
-rw-r--r--glance/tests/unit/async_/flows/test_ovf_process.py (renamed from glance/tests/unit/async/flows/test_ovf_process.py)2
-rw-r--r--glance/tests/unit/async_/flows/test_web_download.py (renamed from glance/tests/unit/async/flows/test_web_download.py)2
-rw-r--r--glance/tests/unit/async_/test_async.py (renamed from glance/tests/unit/async/test_async.py)12
-rw-r--r--glance/tests/unit/async_/test_taskflow_executor.py (renamed from glance/tests/unit/async/test_taskflow_executor.py)2
-rw-r--r--glance/tests/unit/base.py22
-rw-r--r--glance/tests/unit/common/test_utils.py2
-rw-r--r--glance/tests/unit/common/test_wsgi.py62
-rw-r--r--glance/tests/unit/test_data_migration_version.py59
-rw-r--r--glance/tests/unit/test_domain.py14
-rw-r--r--glance/tests/unit/test_notifier.py6
-rw-r--r--glance/tests/unit/test_quota.py20
-rw-r--r--glance/tests/unit/v2/test_images_resource.py385
-rw-r--r--glance/tests/utils.py28
-rw-r--r--lower-constraints.txt4
-rw-r--r--releasenotes/notes/bp-upgrade-checks-b3272c3ddb4e8cf7.yaml9
-rw-r--r--releasenotes/notes/deprecate-show-multiple-location-continued-646f91b21cd771f7.yaml23
-rw-r--r--releasenotes/notes/use-webob-1.8.1-5c3cd1b1382f063e.yaml12
-rw-r--r--releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po35
-rw-r--r--requirements.txt6
-rw-r--r--setup.cfg22
-rw-r--r--test-requirements.txt1
-rw-r--r--tox.ini14
78 files changed, 1360 insertions, 311 deletions
diff --git a/.zuul.yaml b/.zuul.yaml
index 225b140..f65fe4e 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -26,6 +26,7 @@
26- job: 26- job:
27 name: glance-tox-oslo-tips-base 27 name: glance-tox-oslo-tips-base
28 parent: tox 28 parent: tox
29 abstract: true
29 description: Abstract job for Glance vs. oslo libraries 30 description: Abstract job for Glance vs. oslo libraries
30 # NOTE(rosmaita): only need functional test jobs, oslo is 31 # NOTE(rosmaita): only need functional test jobs, oslo is
31 # already running periodic jobs using our unit tests 32 # already running periodic jobs using our unit tests
@@ -65,6 +66,7 @@
65- job: 66- job:
66 name: glance-tox-keystone-tips-base 67 name: glance-tox-keystone-tips-base
67 parent: tox 68 parent: tox
69 abstract: true
68 description: Abstract job for Glance vs. keystone 70 description: Abstract job for Glance vs. keystone
69 required-projects: 71 required-projects:
70 - name: openstack/keystoneauth 72 - name: openstack/keystoneauth
@@ -106,6 +108,7 @@
106- job: 108- job:
107 name: glance-tox-glance_store-tips-base 109 name: glance-tox-glance_store-tips-base
108 parent: tox 110 parent: tox
111 abstract: true
109 description: Abstract job for Glance vs. glance_store 112 description: Abstract job for Glance vs. glance_store
110 required-projects: 113 required-projects:
111 - name: openstack/glance_store 114 - name: openstack/glance_store
@@ -145,6 +148,7 @@
145- job: 148- job:
146 name: glance-tox-cursive-tips-base 149 name: glance-tox-cursive-tips-base
147 parent: tox 150 parent: tox
151 abstract: true
148 description: Abstract job for Glance vs. cursive and related libs 152 description: Abstract job for Glance vs. cursive and related libs
149 required-projects: 153 required-projects:
150 - name: openstack/cursive 154 - name: openstack/cursive
@@ -184,35 +188,144 @@
184 tox_envlist: functional-py35 188 tox_envlist: functional-py35
185 189
186- project: 190- project:
191 templates:
192 - check-requirements
193 - integrated-gate
194 - integrated-gate-py3
195 - openstack-lower-constraints-jobs
196 - openstack-python-jobs
197 - openstack-python35-jobs
198 - openstack-python36-jobs
199 - periodic-stable-jobs
200 - publish-openstack-docs-pti
201 - release-notes-jobs-python3
187 check: 202 check:
188 jobs: 203 jobs:
189 - openstack-tox-functional 204 - openstack-tox-functional
190 - openstack-tox-functional-py35 205 - openstack-tox-functional-py35
191 - openstack-tox-lower-constraints
192 - glance-eventlet-ssl-handshake-broken-py35: 206 - glance-eventlet-ssl-handshake-broken-py35:
193 voting: false 207 voting: false
208 - devstack-plugin-ceph-tempest:
209 voting: false
210 irrelevant-files:
211 - ^(test-|)requirements.txt$
212 - ^.*\.rst$
213 - ^api-ref/.*$
214 - ^doc/.*$
215 - ^releasenotes/.*$
216 - ^setup.cfg$
217 - ^tox.ini$
218 - tempest-full:
219 irrelevant-files:
220 - ^(test-|)requirements.txt$
221 - ^.*\.rst$
222 - ^api-ref/.*$
223 - ^doc/.*$
224 - ^releasenotes/.*$
225 - ^setup.cfg$
226 - ^tox.ini$
227 - neutron-grenade:
228 irrelevant-files:
229 - ^(test-|)requirements.txt$
230 - ^.*\.rst$
231 - ^api-ref/.*$
232 - ^doc/.*$
233 - ^releasenotes/.*$
234 - ^setup.cfg$
235 - ^tox.ini$
194 gate: 236 gate:
195 jobs: 237 jobs:
196 - openstack-tox-functional 238 - openstack-tox-functional
197 - openstack-tox-functional-py35 239 - openstack-tox-functional-py35
198 - openstack-tox-lower-constraints 240 - tempest-full:
241 irrelevant-files:
242 - ^(test-|)requirements.txt$
243 - ^.*\.rst$
244 - ^api-ref/.*$
245 - ^doc/.*$
246 - ^releasenotes/.*$
247 - ^setup.cfg$
248 - ^tox.ini$
249 - neutron-grenade:
250 irrelevant-files:
251 - ^(test-|)requirements.txt$
252 - ^.*\.rst$
253 - ^api-ref/.*$
254 - ^doc/.*$
255 - ^releasenotes/.*$
256 - ^setup.cfg$
257 - ^tox.ini$
258
199 experimental: 259 experimental:
200 jobs: 260 jobs:
201 - barbican-simple-crypto-devstack-tempest 261 - barbican-simple-crypto-devstack-tempest
202 - glance-dsvm-grenade-multinode 262 - glance-dsvm-grenade-multinode
263 - devstack-plugin-ceph-tempest-py3:
264 irrelevant-files:
265 - ^(test-|)requirements.txt$
266 - ^.*\.rst$
267 - ^api-ref/.*$
268 - ^doc/.*$
269 - ^releasenotes/.*$
270 - ^setup.cfg$
271 - ^tox.ini$
272 - tempest-pg-full:
273 irrelevant-files:
274 - ^(test-|)requirements.txt$
275 - ^.*\.rst$
276 - ^api-ref/.*$
277 - ^doc/.*$
278 - ^releasenotes/.*$
279 - ^setup.cfg$
280 - ^tox.ini$
281 - tempest-full-py3-opensuse150:
282 irrelevant-files:
283 - ^(test-|)requirements.txt$
284 - ^.*\.rst$
285 - ^api-ref/.*$
286 - ^doc/.*$
287 - ^releasenotes/.*$
288 - ^setup.cfg$
289 - ^tox.ini$
203 periodic: 290 periodic:
204 jobs: 291 jobs:
205 - glance-tox-functional-oslo-tips 292 # NOTE(rosmaita): we only want the "tips" jobs to be run against
206 - glance-tox-functional-py35-oslo-tips 293 # master, hence the 'branches' qualifiers below. Without them, when
207 - glance-tox-py27-keystone-tips 294 # a stable branch is cut, the tests would be run against the stable
208 - glance-tox-py35-keystone-tips 295 # branch as well, which is pointless because these libraries are
209 - glance-tox-functional-keystone-tips 296 # frozen (more or less) in the stable branches.
210 - glance-tox-functional-py35-keystone-tips 297 #
211 - glance-tox-py27-glance_store-tips 298 # The "tips" jobs can be removed from the stable branch .zuul.yaml
212 - glance-tox-py35-glance_store-tips 299 # files if someone is so inclined, but that would require manual
213 - glance-tox-functional-glance_store-tips 300 # maintenance, so we do not do it by default. Another option is
214 - glance-tox-functional-py35-glance_store-tips 301 # to define these jobs in the openstack-infra/project-config repo.
215 - glance-tox-py27-cursive-tips 302 # That would make us less agile in adjusting these tests, so we
216 - glance-tox-py35-cursive-tips 303 # aren't doing that either.
217 - glance-tox-functional-cursive-tips 304 - glance-tox-functional-oslo-tips:
218 - glance-tox-functional-py35-cursive-tips 305 branches: master
306 - glance-tox-functional-py35-oslo-tips:
307 branches: master
308 - glance-tox-py27-keystone-tips:
309 branches: master
310 - glance-tox-py35-keystone-tips:
311 branches: master
312 - glance-tox-functional-keystone-tips:
313 branches: master
314 - glance-tox-functional-py35-keystone-tips:
315 branches: master
316 - glance-tox-py27-glance_store-tips:
317 branches: master
318 - glance-tox-py35-glance_store-tips:
319 branches: master
320 - glance-tox-functional-glance_store-tips:
321 branches: master
322 - glance-tox-functional-py35-glance_store-tips:
323 branches: master
324 - glance-tox-py27-cursive-tips:
325 branches: master
326 - glance-tox-py35-cursive-tips:
327 branches: master
328 - glance-tox-functional-cursive-tips:
329 branches: master
330 - glance-tox-functional-py35-cursive-tips:
331 branches: master
diff --git a/README.rst b/README.rst
index 7c9bc11..c267df7 100644
--- a/README.rst
+++ b/README.rst
@@ -71,7 +71,7 @@ Release notes
71To learn more about Glance's new features, optimizations, and changes 71To learn more about Glance's new features, optimizations, and changes
72between versions, consult the release notes online at: 72between versions, consult the release notes online at:
73 73
74* `Release Notes <https://docs.openstack.org/releasenotes/glance/>`_ 74* `Release Notes <https://docs.openstack.org/releasenotes/glance/>`__
75 75
76Other Information 76Other Information
77----------------- 77-----------------
diff --git a/doc/source/admin/interoperable-image-import.rst b/doc/source/admin/interoperable-image-import.rst
index f93fdc0..b1b2ae2 100644
--- a/doc/source/admin/interoperable-image-import.rst
+++ b/doc/source/admin/interoperable-image-import.rst
@@ -243,17 +243,17 @@ specified in the ``glance-image-import.conf`` file. The plugins are loaded by
243the Glance project team, these are already registered for you.) 243the Glance project team, these are already registered for you.)
244 244
245A plugin must be written in Python as a `Taskflow "Task" object`_. The file 245A plugin must be written in Python as a `Taskflow "Task" object`_. The file
246containing this object must be present in the ``glance/async/flows/plugins`` 246containing this object must be present in the ``glance/async_/flows/plugins``
247directory. The plugin file must contain a ``get_flow`` function that returns a 247directory. The plugin file must contain a ``get_flow`` function that returns a
248Taskflow Task object wrapped in a linear flow. See the ``no_op`` plugin, 248Taskflow Task object wrapped in a linear flow. See the ``no_op`` plugin,
249located at ``glance/async/flows/plugins/no_op.py`` for an example of how to do 249located at ``glance/async_/flows/plugins/no_op.py`` for an example of how to do
250this. 250this.
251 251
252Specifying the plugins to be used 252Specifying the plugins to be used
253~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 253~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
254 254
255First, the plugin code must exist in the directory 255First, the plugin code must exist in the directory
256``glance/async/flows/plugins``. The name of a plugin is the filename (without 256``glance/async_/flows/plugins``. The name of a plugin is the filename (without
257extension) of the file containing the plugin code. For example, a file named 257extension) of the file containing the plugin code. For example, a file named
258``fred_mertz.py`` would contain the plugin ``fred_mertz``. 258``fred_mertz.py`` would contain the plugin ``fred_mertz``.
259 259
@@ -269,7 +269,7 @@ of the form::
269 269
270For example:: 270For example::
271 271
272 no_op = glance.async.flows.plugins.no_op:get_flow 272 no_op = glance.async_.flows.plugins.no_op:get_flow
273 273
274Make sure any plugin you want to use is included here. 274Make sure any plugin you want to use is included here.
275 275
@@ -337,7 +337,7 @@ required.
337 The first section, ``image_import_opts``, is used to enable the plugin by 337 The first section, ``image_import_opts``, is used to enable the plugin by
338 specifying the plugin name as one of the elements of the list that is the 338 specifying the plugin name as one of the elements of the list that is the
339 value of the `image_import_plugins` parameter. The plugin name is simply 339 value of the `image_import_plugins` parameter. The plugin name is simply
340 the module name under glance/async/flows/plugins/ 340 the module name under glance/async\_/flows/plugins/
341 341
342 The second section, ``inject_metadata_properties``, is where you set the 342 The second section, ``inject_metadata_properties``, is where you set the
343 parameters for the injection plugin. (Note that the values you specify here 343 parameters for the injection plugin. (Note that the values you specify here
diff --git a/doc/source/admin/multistores.rst b/doc/source/admin/multistores.rst
index c6d9a8e..4963b76 100644
--- a/doc/source/admin/multistores.rst
+++ b/doc/source/admin/multistores.rst
@@ -70,7 +70,7 @@ operators to enable multiple stores support.
70 70
71 .. code-block:: ini 71 .. code-block:: ini
72 72
73 [DEFAULT] 73 [glance_store]
74 default_backend = fast 74 default_backend = fast
75 75
76* For each of the store identifier defined in ``enabled_backends`` section 76* For each of the store identifier defined in ``enabled_backends`` section
diff --git a/doc/source/admin/useful-image-properties.rst b/doc/source/admin/useful-image-properties.rst
index 88c37c4..63d5d35 100644
--- a/doc/source/admin/useful-image-properties.rst
+++ b/doc/source/admin/useful-image-properties.rst
@@ -345,6 +345,14 @@ Here is a list of useful image properties and the values they expect.
345 rng_dev_path=/dev/hwrng 345 rng_dev_path=/dev/hwrng
346 346
347 - ``virtio``, or other supported device. 347 - ``virtio``, or other supported device.
348 * - libvirt API driver
349 - ``hw_time_hpet``
350 - Adds support for the High Precision Event Timer (HPET) for x86 guests
351 in the libvirt driver when ``hypervisor_type=qemu`` and
352 ``architecture=i686`` or ``architecture=x86_64``. The timer can be
353 enabled by setting ``hw_time_hpet=true``. By default HPET remains
354 disabled.
355 - ``true`` or ``false`` (default)
348 * - libvirt API driver, Hyper-V driver 356 * - libvirt API driver, Hyper-V driver
349 - ``hw_machine_type`` 357 - ``hw_machine_type``
350 - For libvirt: Enables booting an ARM system using the specified machine 358 - For libvirt: Enables booting an ARM system using the specified machine
diff --git a/doc/source/cli/glancestatus.rst b/doc/source/cli/glancestatus.rst
new file mode 100644
index 0000000..6aa5e87
--- /dev/null
+++ b/doc/source/cli/glancestatus.rst
@@ -0,0 +1,95 @@
1=============
2glance-status
3=============
4
5---------------------
6Glance Status Utility
7---------------------
8
9.. include:: header.txt
10
11SYNOPSIS
12========
13
14::
15
16 glance-status [options]
17
18DESCRIPTION
19===========
20
21``glance-status`` is a command line utility to aid operators in upgrading
22glance by running programmable checks for things that might prevent upgrades.
23
24COMMANDS
25========
26
27``upgrade``
28 This is the prefix for checking the glance deployment for any upgrade
29 issues that might prevent glance from upgrading.
30
31You can also run with a category argument such as ``upgrade`` to see a list of
32all commands in that category::
33
34 glance-status upgrade
35
36These sections describe the available categories and arguments for
37:command:`glance-status`.
38
39Categories and commands
40-----------------------
41
42``glance-status upgrade check``
43 Performs a release-specific readiness check before restarting services with
44 new code, or upgrading. This command expects to have complete configuration
45 and access to the database.
46
47 **Return Codes**
48
49 .. list-table::
50 :widths: 20 80
51 :header-rows: 1
52
53 * - Return code
54 - Description
55 * - 0
56 - All upgrade readiness checks passed successfully and there is nothing
57 to do.
58 * - 1
59 - At least one check encountered an issue and requires further
60 investigation. This is considered a warning but the upgrade may be OK.
61 * - 2
62 - There was an upgrade status check failure that needs to be
63 investigated. This should be considered something that stops an
64 upgrade.
65 * - 255
66 - An unexpected error occurred.
67
68 **History of Checks**
69
70 **18.0.0 (Stein)**
71
72 * Placeholder to be filled in with checks as they are added in Stein.
73
74OPTIONS
75=======
76
77**General Options**
78
79``-h, --help``
80 show this help message and exit
81
82``--config-dir DIR``
83 Path to a config directory to pull `*.conf` files from.
84 This file set is sorted, so as to provide a predictable
85 parse order if individual options are over-ridden. The
86 set is parsed after the file(s) specified via previous
87 --config-file, arguments hence over-ridden options in
88 the directory take precedence.
89
90``--config-file PATH``
91 Path to a config file to use. Multiple config files can
92 be specified, with values in later files taking
93 precedence. Defaults to None.
94
95.. include:: footer.txt
diff --git a/doc/source/contributor/blueprints.rst b/doc/source/contributor/blueprints.rst
index 9bf3804..a0f6484 100644
--- a/doc/source/contributor/blueprints.rst
+++ b/doc/source/contributor/blueprints.rst
@@ -62,7 +62,7 @@ Glance project and your role (user, developer, deployer, operator,
62etc.), you are more than welcome to have a preliminary discussion of a 62etc.), you are more than welcome to have a preliminary discussion of a
63potential lite spec by reaching out to other people involved in the 63potential lite spec by reaching out to other people involved in the
64project. This usually happens by posting mails on the relevant mailing 64project. This usually happens by posting mails on the relevant mailing
65lists (e.g. `openstack-dev <http://lists.openstack.org>`_ - include 65lists (e.g. `openstack-discuss <http://lists.openstack.org>`_ - include
66[glance] in the subject) or on #openstack-glance IRC channel on 66[glance] in the subject) or on #openstack-glance IRC channel on
67Freenode. If current ongoing code reviews are related to your feature, 67Freenode. If current ongoing code reviews are related to your feature,
68posting comments/questions on gerrit may also be a way to engage. Some 68posting comments/questions on gerrit may also be a way to engage. Some
diff --git a/doc/source/contributor/minor-code-changes.rst b/doc/source/contributor/minor-code-changes.rst
index b75147c..4afcb06 100644
--- a/doc/source/contributor/minor-code-changes.rst
+++ b/doc/source/contributor/minor-code-changes.rst
@@ -94,3 +94,15 @@ Python 3. While the deprecation is real, Glance uses oslo_log that provides
94alias warn and solves the issue in single place for all projects using it. 94alias warn and solves the issue in single place for all projects using it.
95These changes are not accepted due to the huge amount of refactoring they 95These changes are not accepted due to the huge amount of refactoring they
96cause for no reason. 96cause for no reason.
97
98Gratuitious use of oslo libraries
99---------------------------------
100
101We are big fans of the oslo libraries and all the hard work the Oslo team does
102to keep common code reusable and easily consumable. But that doesn't mean that
103it's a bug if Glance isn't using an oslo library everywhere you could possibly
104use one. We are all for using oslo if it provides any level of benefit for us
105and makes sense, but please let's not have these bugs/patches of "Let's use
106oslo because it exists".
107
108
diff --git a/doc/source/user/glancemetadefcatalogapi.rst b/doc/source/user/glancemetadefcatalogapi.rst
index 11ccab8..fc3206a 100644
--- a/doc/source/user/glancemetadefcatalogapi.rst
+++ b/doc/source/user/glancemetadefcatalogapi.rst
@@ -25,8 +25,8 @@ services, and projects for OpenStack users.
25 25
26This is about the definition of the available metadata that can be used on 26This is about the definition of the available metadata that can be used on
27different types of resources (images, artifacts, volumes, flavors, aggregates, 27different types of resources (images, artifacts, volumes, flavors, aggregates,
28etc). A definition includes the properties type, its key, it's description, 28etc). A definition includes the properties type, its key, its description,
29and it's constraints. This catalog will not store the values for specific 29and its constraints. This catalog will not store the values for specific
30instance properties. 30instance properties.
31 31
32For example, a definition of a virtual CPU topology property for number of 32For example, a definition of a virtual CPU topology property for number of
diff --git a/etc/glance-image-import.conf.sample b/etc/glance-image-import.conf.sample
index fd27ad0..19a6420 100644
--- a/etc/glance-image-import.conf.sample
+++ b/etc/glance-image-import.conf.sample
@@ -20,7 +20,7 @@
20# Possible values: 20# Possible values:
21# * qcow2 21# * qcow2
22# * raw 22# * raw
23# * vdmk 23# * vmdk
24# 24#
25# Related Options: 25# Related Options:
26# * disk_formats 26# * disk_formats
@@ -28,7 +28,7 @@
28# Possible values: 28# Possible values:
29# qcow2 - <No description provided> 29# qcow2 - <No description provided>
30# raw - <No description provided> 30# raw - <No description provided>
31# vdmk - <No description provided> 31# vmdk - <No description provided>
32#output_format = raw 32#output_format = raw
33 33
34 34
@@ -60,6 +60,10 @@
60# * Any provided Task object name to be included 60# * Any provided Task object name to be included
61# in to the flow. 61# in to the flow.
62# (list value) 62# (list value)
63#
64# This option has a sample default set, which means that
65# its actual default value may vary from the one documented
66# below.
63#image_import_plugins = [no_op] 67#image_import_plugins = [no_op]
64 68
65 69
diff --git a/etc/oslo-config-generator/glance-api.conf b/etc/oslo-config-generator/glance-api.conf
index 31bdbaa..324a14e 100644
--- a/etc/oslo-config-generator/glance-api.conf
+++ b/etc/oslo-config-generator/glance-api.conf
@@ -3,6 +3,7 @@ wrap_width = 80
3output_file = etc/glance-api.conf.sample 3output_file = etc/glance-api.conf.sample
4namespace = glance.api 4namespace = glance.api
5namespace = glance.store 5namespace = glance.store
6namespace = glance.multi_store
6namespace = oslo.concurrency 7namespace = oslo.concurrency
7namespace = oslo.messaging 8namespace = oslo.messaging
8namespace = oslo.db 9namespace = oslo.db
diff --git a/etc/rootwrap.conf b/etc/rootwrap.conf
deleted file mode 100644
index 7ba70fb..0000000
--- a/etc/rootwrap.conf
+++ /dev/null
@@ -1,27 +0,0 @@
1# Configuration for glance-rootwrap
2# This file should be owned by (and only-writable by) the root user
3
4[DEFAULT]
5# List of directories to load filter definitions from (separated by ',').
6# These directories MUST all be only writeable by root !
7filters_path=/etc/glance/rootwrap.d,/usr/share/glance/rootwrap
8
9# List of directories to search executables in, in case filters do not
10# explicitely specify a full path (separated by ',')
11# If not specified, defaults to system PATH environment variable.
12# These directories MUST all be only writeable by root !
13exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin
14
15# Enable logging to syslog
16# Default value is False
17use_syslog=False
18
19# Which syslog facility to use.
20# Valid values include auth, authpriv, syslog, local0, local1...
21# Default value is 'syslog'
22syslog_log_facility=syslog
23
24# Which messages to log.
25# INFO means log all usage
26# ERROR means only log unsuccessful attempts
27syslog_log_level=ERROR
diff --git a/glance/api/v2/images.py b/glance/api/v2/images.py
index f551bb1..b0fb908 100644
--- a/glance/api/v2/images.py
+++ b/glance/api/v2/images.py
@@ -13,6 +13,7 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16import hashlib
16import re 17import re
17 18
18import glance_store 19import glance_store
@@ -45,6 +46,7 @@ CONF.import_opt('disk_formats', 'glance.common.config', group='image_format')
45CONF.import_opt('container_formats', 'glance.common.config', 46CONF.import_opt('container_formats', 'glance.common.config',
46 group='image_format') 47 group='image_format')
47CONF.import_opt('show_multiple_locations', 'glance.common.config') 48CONF.import_opt('show_multiple_locations', 'glance.common.config')
49CONF.import_opt('hashing_algorithm', 'glance.common.config')
48 50
49 51
50class ImagesController(object): 52class ImagesController(object):
@@ -364,6 +366,76 @@ class ImagesController(object):
364 except exception.NotAuthenticated as e: 366 except exception.NotAuthenticated as e:
365 raise webob.exc.HTTPUnauthorized(explanation=e.msg) 367 raise webob.exc.HTTPUnauthorized(explanation=e.msg)
366 368
369 def _validate_validation_data(self, image, locations):
370 val_data = {}
371 for loc in locations:
372 if 'validation_data' not in loc:
373 continue
374 for k, v in loc['validation_data'].items():
375 if val_data.get(k, v) != v:
376 msg = _("Conflicting values for %s") % k
377 raise webob.exc.HTTPConflict(explanation=msg)
378 val_data[k] = v
379
380 # NOTE(imacdonn): values may be provided for items which are
381 # already set, so long as the values exactly match. In this
382 # case, nothing actually needs to be updated, but we should
383 # reject the request if there's an apparent attempt to supply
384 # a different value.
385 new_val_data = {}
386 for k, v in val_data.items():
387 current = getattr(image, k)
388 if v == current:
389 continue
390 if current:
391 msg = _("%s is already set with a different value") % k
392 raise webob.exc.HTTPConflict(explanation=msg)
393 new_val_data[k] = v
394
395 if not new_val_data:
396 return {}
397
398 if image.status != 'queued':
399 msg = _("New value(s) for %s may only be provided when image "
400 "status is 'queued'") % ', '.join(new_val_data.keys())
401 raise webob.exc.HTTPConflict(explanation=msg)
402
403 if 'checksum' in new_val_data:
404 try:
405 checksum_bytes = bytearray.fromhex(new_val_data['checksum'])
406 except ValueError:
407 msg = (_("checksum (%s) is not a valid hexadecimal value") %
408 new_val_data['checksum'])
409 raise webob.exc.HTTPConflict(explanation=msg)
410 if len(checksum_bytes) != 16:
411 msg = (_("checksum (%s) is not the correct size for md5 "
412 "(should be 16 bytes)") %
413 new_val_data['checksum'])
414 raise webob.exc.HTTPConflict(explanation=msg)
415
416 hash_algo = new_val_data.get('os_hash_algo')
417 if hash_algo != CONF['hashing_algorithm']:
418 msg = (_("os_hash_algo must be %(want)s, not %(got)s") %
419 {'want': CONF['hashing_algorithm'], 'got': hash_algo})
420 raise webob.exc.HTTPConflict(explanation=msg)
421
422 try:
423 hash_bytes = bytearray.fromhex(new_val_data['os_hash_value'])
424 except ValueError:
425 msg = (_("os_hash_value (%s) is not a valid hexadecimal value") %
426 new_val_data['os_hash_value'])
427 raise webob.exc.HTTPConflict(explanation=msg)
428 want_size = hashlib.new(hash_algo).digest_size
429 if len(hash_bytes) != want_size:
430 msg = (_("os_hash_value (%(value)s) is not the correct size for "
431 "%(algo)s (should be %(want)d bytes)") %
432 {'value': new_val_data['os_hash_value'],
433 'algo': hash_algo,
434 'want': want_size})
435 raise webob.exc.HTTPConflict(explanation=msg)
436
437 return new_val_data
438
367 def _get_locations_op_pos(self, path_pos, max_pos, allow_max): 439 def _get_locations_op_pos(self, path_pos, max_pos, allow_max):
368 if path_pos is None or max_pos is None: 440 if path_pos is None or max_pos is None:
369 return None 441 return None
@@ -387,10 +459,16 @@ class ImagesController(object):
387 "%s.") % image.status 459 "%s.") % image.status
388 raise webob.exc.HTTPConflict(explanation=msg) 460 raise webob.exc.HTTPConflict(explanation=msg)
389 461
462 val_data = self._validate_validation_data(image, value)
463
390 try: 464 try:
391 # NOTE(flwang): _locations_proxy's setattr method will check if 465 # NOTE(flwang): _locations_proxy's setattr method will check if
392 # the update is acceptable. 466 # the update is acceptable.
393 image.locations = value 467 image.locations = value
468 if image.status == 'queued':
469 for k, v in val_data.items():
470 setattr(image, k, v)
471 image.status = 'active'
394 except (exception.BadStoreUri, exception.DuplicateLocation) as e: 472 except (exception.BadStoreUri, exception.DuplicateLocation) as e:
395 raise webob.exc.HTTPBadRequest(explanation=e.msg) 473 raise webob.exc.HTTPBadRequest(explanation=e.msg)
396 except ValueError as ve: # update image status failed. 474 except ValueError as ve: # update image status failed.
@@ -408,6 +486,8 @@ class ImagesController(object):
408 "%s.") % image.status 486 "%s.") % image.status
409 raise webob.exc.HTTPConflict(explanation=msg) 487 raise webob.exc.HTTPConflict(explanation=msg)
410 488
489 val_data = self._validate_validation_data(image, [value])
490
411 pos = self._get_locations_op_pos(path_pos, 491 pos = self._get_locations_op_pos(path_pos,
412 len(image.locations), True) 492 len(image.locations), True)
413 if pos is None: 493 if pos is None:
@@ -416,6 +496,8 @@ class ImagesController(object):
416 try: 496 try:
417 image.locations.insert(pos, value) 497 image.locations.insert(pos, value)
418 if image.status == 'queued': 498 if image.status == 'queued':
499 for k, v in val_data.items():
500 setattr(image, k, v)
419 image.status = 'active' 501 image.status = 'active'
420 except (exception.BadStoreUri, exception.DuplicateLocation) as e: 502 except (exception.BadStoreUri, exception.DuplicateLocation) as e:
421 raise webob.exc.HTTPBadRequest(explanation=e.msg) 503 raise webob.exc.HTTPBadRequest(explanation=e.msg)
@@ -1162,6 +1244,36 @@ def get_base_properties():
1162 'metadata': { 1244 'metadata': {
1163 'type': 'object', 1245 'type': 'object',
1164 }, 1246 },
1247 'validation_data': {
1248 'description': _(
1249 'Values to be used to populate the corresponding '
1250 'image properties. If the image status is not '
1251 '\'queued\', values must exactly match those '
1252 'already contained in the image properties.'
1253 ),
1254 'type': 'object',
1255 'writeOnly': True,
1256 'additionalProperties': False,
1257 'properties': {
1258 'checksum': {
1259 'type': 'string',
1260 'minLength': 32,
1261 'maxLength': 32,
1262 },
1263 'os_hash_algo': {
1264 'type': 'string',
1265 'maxLength': 64,
1266 },
1267 'os_hash_value': {
1268 'type': 'string',
1269 'maxLength': 128,
1270 },
1271 },
1272 'required': [
1273 'os_hash_algo',
1274 'os_hash_value',
1275 ],
1276 },
1165 }, 1277 },
1166 'required': ['url', 'metadata'], 1278 'required': ['url', 'metadata'],
1167 }, 1279 },
diff --git a/glance/async/__init__.py b/glance/async_/__init__.py
index b637745..b637745 100644
--- a/glance/async/__init__.py
+++ b/glance/async_/__init__.py
diff --git a/glance/async/flows/__init__.py b/glance/async_/flows/__init__.py
index e69de29..e69de29 100644
--- a/glance/async/flows/__init__.py
+++ b/glance/async_/flows/__init__.py
diff --git a/glance/async/flows/_internal_plugins/__init__.py b/glance/async_/flows/_internal_plugins/__init__.py
index 9efada3..9efada3 100644
--- a/glance/async/flows/_internal_plugins/__init__.py
+++ b/glance/async_/flows/_internal_plugins/__init__.py
diff --git a/glance/async/flows/_internal_plugins/web_download.py b/glance/async_/flows/_internal_plugins/web_download.py
index a58e5d0..a58e5d0 100644
--- a/glance/async/flows/_internal_plugins/web_download.py
+++ b/glance/async_/flows/_internal_plugins/web_download.py
diff --git a/glance/async/flows/api_image_import.py b/glance/async_/flows/api_image_import.py
index dc08d37..0511f18 100644
--- a/glance/async/flows/api_image_import.py
+++ b/glance/async_/flows/api_image_import.py
@@ -23,8 +23,8 @@ from taskflow.patterns import linear_flow as lf
23from taskflow import retry 23from taskflow import retry
24from taskflow import task 24from taskflow import task
25 25
26import glance.async.flows._internal_plugins as internal_plugins 26import glance.async_.flows._internal_plugins as internal_plugins
27import glance.async.flows.plugins as import_plugins 27import glance.async_.flows.plugins as import_plugins
28from glance.common import exception 28from glance.common import exception
29from glance.common.scripts.image_import import main as image_import 29from glance.common.scripts.image_import import main as image_import
30from glance.common.scripts import utils as script_utils 30from glance.common.scripts import utils as script_utils
diff --git a/glance/async/flows/base_import.py b/glance/async_/flows/base_import.py
index 18e7655..66a0be1 100644
--- a/glance/async/flows/base_import.py
+++ b/glance/async_/flows/base_import.py
@@ -30,7 +30,7 @@ from taskflow import retry
30from taskflow import task 30from taskflow import task
31from taskflow.types import failure 31from taskflow.types import failure
32 32
33from glance.async import utils 33from glance.async_ import utils
34from glance.common import exception 34from glance.common import exception
35from glance.common.scripts.image_import import main as image_import 35from glance.common.scripts.image_import import main as image_import
36from glance.common.scripts import utils as script_utils 36from glance.common.scripts import utils as script_utils
diff --git a/glance/async/flows/convert.py b/glance/async_/flows/convert.py
index d1b9f1a..d1b9f1a 100644
--- a/glance/async/flows/convert.py
+++ b/glance/async_/flows/convert.py
diff --git a/glance/async/flows/introspect.py b/glance/async_/flows/introspect.py
index 4d39daf..b5f1fb1 100644
--- a/glance/async/flows/introspect.py
+++ b/glance/async_/flows/introspect.py
@@ -21,7 +21,7 @@ from oslo_utils import encodeutils
21from oslo_utils import excutils 21from oslo_utils import excutils
22from taskflow.patterns import linear_flow as lf 22from taskflow.patterns import linear_flow as lf
23 23
24from glance.async import utils 24from glance.async_ import utils
25from glance.i18n import _LE 25from glance.i18n import _LE
26 26
27 27
diff --git a/glance/async/flows/ovf_process.py b/glance/async_/flows/ovf_process.py
index 79357de..79357de 100644
--- a/glance/async/flows/ovf_process.py
+++ b/glance/async_/flows/ovf_process.py
diff --git a/glance/async/flows/plugins/__init__.py b/glance/async_/flows/plugins/__init__.py
index d920cb0..d920cb0 100644
--- a/glance/async/flows/plugins/__init__.py
+++ b/glance/async_/flows/plugins/__init__.py
diff --git a/glance/async/flows/plugins/image_conversion.py b/glance/async_/flows/plugins/image_conversion.py
index 1d7adcf..3bfbc4a 100644
--- a/glance/async/flows/plugins/image_conversion.py
+++ b/glance/async_/flows/plugins/image_conversion.py
@@ -24,7 +24,7 @@ from oslo_utils import excutils
24from taskflow.patterns import linear_flow as lf 24from taskflow.patterns import linear_flow as lf
25from taskflow import task 25from taskflow import task
26 26
27from glance.async import utils 27from glance.async_ import utils
28from glance.i18n import _ 28from glance.i18n import _
29 29
30LOG = logging.getLogger(__name__) 30LOG = logging.getLogger(__name__)
@@ -32,7 +32,7 @@ LOG = logging.getLogger(__name__)
32conversion_plugin_opts = [ 32conversion_plugin_opts = [
33 cfg.StrOpt('output_format', 33 cfg.StrOpt('output_format',
34 default='raw', 34 default='raw',
35 choices=('qcow2', 'raw', 'vdmk'), 35 choices=('qcow2', 'raw', 'vmdk'),
36 help=_(""" 36 help=_("""
37Desired output format for image conversion plugin. 37Desired output format for image conversion plugin.
38 38
@@ -46,7 +46,7 @@ the conversion and import will fail.
46Possible values: 46Possible values:
47 * qcow2 47 * qcow2
48 * raw 48 * raw
49 * vdmk 49 * vmdk
50 50
51Related Options: 51Related Options:
52 * disk_formats 52 * disk_formats
@@ -75,7 +75,7 @@ class _ConvertImage(task.Task):
75 75
76 def execute(self, file_path, **kwargs): 76 def execute(self, file_path, **kwargs):
77 77
78 target_format = CONF.conversion_plugin_options.output_format 78 target_format = CONF.image_conversion.output_format
79 # TODO(jokke): Once we support other schemas we need to take them into 79 # TODO(jokke): Once we support other schemas we need to take them into
80 # account and handle the paths here. 80 # account and handle the paths here.
81 src_path = file_path.split('file://')[-1] 81 src_path = file_path.split('file://')[-1]
diff --git a/glance/async/flows/plugins/inject_image_metadata.py b/glance/async_/flows/plugins/inject_image_metadata.py
index 24010a8..24010a8 100644
--- a/glance/async/flows/plugins/inject_image_metadata.py
+++ b/glance/async_/flows/plugins/inject_image_metadata.py
diff --git a/glance/async/flows/plugins/no_op.py b/glance/async_/flows/plugins/no_op.py
index 35c290f..35c290f 100644
--- a/glance/async/flows/plugins/no_op.py
+++ b/glance/async_/flows/plugins/no_op.py
diff --git a/glance/async/flows/plugins/plugin_opts.py b/glance/async_/flows/plugins/plugin_opts.py
index 709b78c..173f23c 100644
--- a/glance/async/flows/plugins/plugin_opts.py
+++ b/glance/async_/flows/plugins/plugin_opts.py
@@ -14,8 +14,8 @@
14# under the License. 14# under the License.
15 15
16 16
17import glance.async.flows.plugins.image_conversion 17import glance.async_.flows.plugins.image_conversion
18import glance.async.flows.plugins.inject_image_metadata 18import glance.async_.flows.plugins.inject_image_metadata
19 19
20 20
21# Note(jokke): This list contains tuples of config options for import plugins. 21# Note(jokke): This list contains tuples of config options for import plugins.
@@ -27,9 +27,9 @@ import glance.async.flows.plugins.inject_image_metadata
27# registered under at the config file. 27# registered under at the config file.
28PLUGIN_OPTS = [ 28PLUGIN_OPTS = [
29 ('inject_metadata_properties', 29 ('inject_metadata_properties',
30 glance.async.flows.plugins.inject_image_metadata.inject_metadata_opts), 30 glance.async_.flows.plugins.inject_image_metadata.inject_metadata_opts),
31 ('image_conversion', 31 ('image_conversion',
32 glance.async.flows.plugins.image_conversion.conversion_plugin_opts), 32 glance.async_.flows.plugins.image_conversion.conversion_plugin_opts),
33] 33]
34 34
35 35
diff --git a/glance/async/taskflow_executor.py b/glance/async_/taskflow_executor.py
index 3fe810d..943c33c 100644
--- a/glance/async/taskflow_executor.py
+++ b/glance/async_/taskflow_executor.py
@@ -23,7 +23,7 @@ from stevedore import driver
23from taskflow import engines 23from taskflow import engines
24from taskflow.listeners import logging as llistener 24from taskflow.listeners import logging as llistener
25 25
26import glance.async 26import glance.async_
27from glance.common import exception 27from glance.common import exception
28from glance.common.scripts import utils as script_utils 28from glance.common.scripts import utils as script_utils
29from glance.i18n import _, _LE 29from glance.i18n import _, _LE
@@ -85,7 +85,7 @@ CONF = cfg.CONF
85CONF.register_opts(taskflow_executor_opts, group='taskflow_executor') 85CONF.register_opts(taskflow_executor_opts, group='taskflow_executor')
86 86
87 87
88class TaskExecutor(glance.async.TaskExecutor): 88class TaskExecutor(glance.async_.TaskExecutor):
89 89
90 def __init__(self, context, task_repo, image_repo, image_factory): 90 def __init__(self, context, task_repo, image_repo, image_factory):
91 self.context = context 91 self.context = context
diff --git a/glance/async/utils.py b/glance/async_/utils.py
index 6a5054e..6a5054e 100644
--- a/glance/async/utils.py
+++ b/glance/async_/utils.py
diff --git a/glance/cmd/__init__.py b/glance/cmd/__init__.py
index e1aeec3..e69de29 100644
--- a/glance/cmd/__init__.py
+++ b/glance/cmd/__init__.py
@@ -1,18 +0,0 @@
1# Copyright 2013 OpenStack Foundation
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16from glance import i18n
17
18i18n.enable_lazy()
diff --git a/glance/cmd/status.py b/glance/cmd/status.py
new file mode 100644
index 0000000..c2b03d5
--- /dev/null
+++ b/glance/cmd/status.py
@@ -0,0 +1,28 @@
1#!/usr/bin/env python
2
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15from oslo_config import cfg
16from oslo_upgradecheck import upgradecheck
17
18CONF = cfg.CONF
19
20
21class Checks(upgradecheck.UpgradeCommands):
22 """Programmable upgrade checks."""
23
24 pass
25
26
27def main():
28 return upgradecheck.main(CONF, 'glance', Checks())
diff --git a/glance/common/config.py b/glance/common/config.py
index bf341cb..5ef92d9 100644
--- a/glance/common/config.py
+++ b/glance/common/config.py
@@ -405,14 +405,26 @@ Related options:
405""")), 405""")),
406 # NOTE(flaper87): The policy.json file should be updated and the locaiton 406 # NOTE(flaper87): The policy.json file should be updated and the locaiton
407 # related rules set to admin only once this option is finally removed. 407 # related rules set to admin only once this option is finally removed.
408 # NOTE(rosmaita): Unfortunately, this option is used to gate some code
409 # paths; if the location related policies are set admin-only, then no
410 # normal users can save or retrieve image data.
408 cfg.BoolOpt('show_multiple_locations', default=False, 411 cfg.BoolOpt('show_multiple_locations', default=False,
409 deprecated_for_removal=True, 412 deprecated_for_removal=True,
410 deprecated_reason=_('This option will be removed in the Pike ' 413 deprecated_reason=_('Use of this option, deprecated since '
411 'release or later because the same ' 414 'Newton, is a security risk and will be '
412 'functionality can be achieved with ' 415 'removed once we figure out a way to '
413 'greater granularity by using policies. ' 416 'satisfy those use cases that currently '
414 'Please see the Newton ' 417 'require it. An earlier announcement '
415 'release notes for more information.'), 418 'that the same functionality can be '
419 'achieved with greater granularity by '
420 'using policies is incorrect. You cannot '
421 'work around this option via policy '
422 'configuration at the present time, '
423 'though that is the direction we believe '
424 'the fix will take. Please keep an eye '
425 'on the Glance release notes to stay up '
426 'to date on progress in addressing this '
427 'issue.'),
416 deprecated_since='Newton', 428 deprecated_since='Newton',
417 help=_(""" 429 help=_("""
418Show all image locations when returning an image. 430Show all image locations when returning an image.
@@ -429,6 +441,8 @@ NOTES:
429 image locations can sometimes include credentials. Hence, this 441 image locations can sometimes include credentials. Hence, this
430 is set to ``False`` by default. Set this to ``True`` with 442 is set to ``False`` by default. Set this to ``True`` with
431 EXTREME CAUTION and ONLY IF you know what you are doing! 443 EXTREME CAUTION and ONLY IF you know what you are doing!
444 * See https://wiki.openstack.org/wiki/OSSN/OSSN-0065 for more
445 information.
432 * If an operator wishes to avoid showing any image location(s) 446 * If an operator wishes to avoid showing any image location(s)
433 to the user, then both this option and 447 to the user, then both this option and
434 ``show_image_direct_url`` MUST be set to ``False``. 448 ``show_image_direct_url`` MUST be set to ``False``.
diff --git a/glance/common/timeutils.py b/glance/common/timeutils.py
index d3d865c..93ab868 100644
--- a/glance/common/timeutils.py
+++ b/glance/common/timeutils.py
@@ -20,7 +20,6 @@ Time related utilities and helper functions.
20import datetime 20import datetime
21 21
22import iso8601 22import iso8601
23from monotonic import monotonic as now # noqa
24from oslo_utils import encodeutils 23from oslo_utils import encodeutils
25 24
26# ISO 8601 extended time format with microseconds 25# ISO 8601 extended time format with microseconds
diff --git a/glance/common/utils.py b/glance/common/utils.py
index 05ff3df..92df5bf 100644
--- a/glance/common/utils.py
+++ b/glance/common/utils.py
@@ -128,7 +128,7 @@ def cooperative_read(fd):
128MAX_COOP_READER_BUFFER_SIZE = 134217728 # 128M seems like a sane buffer limit 128MAX_COOP_READER_BUFFER_SIZE = 134217728 # 128M seems like a sane buffer limit
129 129
130CONF.import_group('import_filtering_opts', 130CONF.import_group('import_filtering_opts',
131 'glance.async.flows._internal_plugins') 131 'glance.async_.flows._internal_plugins')
132 132
133 133
134def validate_import_uri(uri): 134def validate_import_uri(uri):
diff --git a/glance/common/wsgi.py b/glance/common/wsgi.py
index bc0bb08..bda6f03 100644
--- a/glance/common/wsgi.py
+++ b/glance/common/wsgi.py
@@ -53,12 +53,6 @@ from glance import i18n
53from glance.i18n import _, _LE, _LI, _LW 53from glance.i18n import _, _LE, _LI, _LW
54 54
55 55
56try:
57 from webob.acceptparse import AcceptLanguageValidHeader # noqa
58 USING_WEBOB_1_8 = True
59except ImportError:
60 USING_WEBOB_1_8 = False
61
62bind_opts = [ 56bind_opts = [
63 cfg.HostAddressOpt('bind_host', 57 cfg.HostAddressOpt('bind_host',
64 default='0.0.0.0', 58 default='0.0.0.0',
@@ -320,7 +314,7 @@ wsgi_opts = [
320store_opts = [ 314store_opts = [
321 cfg.DictOpt('enabled_backends', 315 cfg.DictOpt('enabled_backends',
322 help=_('Key:Value pair of store identifier and store type. ' 316 help=_('Key:Value pair of store identifier and store type. '
323 'In case of multiple backends should be separated' 317 'In case of multiple backends should be separated '
324 'using comma.')), 318 'using comma.')),
325] 319]
326 320
@@ -1033,8 +1027,10 @@ class Request(webob.Request):
1033 def best_match_content_type(self): 1027 def best_match_content_type(self):
1034 """Determine the requested response content-type.""" 1028 """Determine the requested response content-type."""
1035 supported = ('application/json',) 1029 supported = ('application/json',)
1036 bm = self.accept.best_match(supported) 1030 best_matches = self.accept.acceptable_offers(supported)
1037 return bm or 'application/json' 1031 if not best_matches:
1032 return 'application/json'
1033 return best_matches[0][0]
1038 1034
1039 def get_content_type(self, allowed_content_types): 1035 def get_content_type(self, allowed_content_types):
1040 """Determine content type of the request body.""" 1036 """Determine content type of the request body."""
@@ -1048,18 +1044,7 @@ class Request(webob.Request):
1048 else: 1044 else:
1049 return content_type 1045 return content_type
1050 1046
1051 def _best_match_language_1_7(self): 1047 def best_match_language(self):
1052 """Determines best available locale from the Accept-Language header.
1053
1054 :returns: the best language match or None if the 'Accept-Language'
1055 header was not available in the request.
1056 """
1057 if not self.accept_language:
1058 return None
1059 langs = i18n.get_available_languages('glance')
1060 return self.accept_language.best_match(langs)
1061
1062 def _best_match_language_1_8(self):
1063 """Determines best available locale from the Accept-Language header. 1048 """Determines best available locale from the Accept-Language header.
1064 1049
1065 :returns: the best language match or None if the 'Accept-Language' 1050 :returns: the best language match or None if the 'Accept-Language'
@@ -1076,12 +1061,6 @@ class Request(webob.Request):
1076 best_match = None 1061 best_match = None
1077 return best_match 1062 return best_match
1078 1063
1079 def best_match_language(self):
1080 if USING_WEBOB_1_8:
1081 return self._best_match_language_1_8()
1082 else:
1083 return self._best_match_language_1_7()
1084
1085 def get_range_from_request(self, image_size): 1064 def get_range_from_request(self, image_size):
1086 """Return the `Range` in a request.""" 1065 """Return the `Range` in a request."""
1087 1066
diff --git a/glance/db/migration.py b/glance/db/migration.py
index fdaea24..79d4584 100644
--- a/glance/db/migration.py
+++ b/glance/db/migration.py
@@ -47,7 +47,7 @@ def get_backend():
47# Migration-related constants 47# Migration-related constants
48EXPAND_BRANCH = 'expand' 48EXPAND_BRANCH = 'expand'
49CONTRACT_BRANCH = 'contract' 49CONTRACT_BRANCH = 'contract'
50CURRENT_RELEASE = 'rocky' 50CURRENT_RELEASE = 'stein'
51ALEMBIC_INIT_VERSION = 'liberty' 51ALEMBIC_INIT_VERSION = 'liberty'
52LATEST_REVISION = 'queens_contract01' 52LATEST_REVISION = 'queens_contract01'
53INIT_VERSION = 0 53INIT_VERSION = 0
diff --git a/glance/db/sqlalchemy/api.py b/glance/db/sqlalchemy/api.py
index 9cddd2b..28fe3cc 100644
--- a/glance/db/sqlalchemy/api.py
+++ b/glance/db/sqlalchemy/api.py
@@ -1322,6 +1322,35 @@ def purge_deleted_rows(context, age_in_days, max_rows, session=None):
1322 continue 1322 continue
1323 if hasattr(model_class, 'deleted'): 1323 if hasattr(model_class, 'deleted'):
1324 tables.append(model_class.__tablename__) 1324 tables.append(model_class.__tablename__)
1325
1326 # First force purging of records that are not soft deleted but
1327 # are referencing soft deleted tasks/images records (e.g. task_info
1328 # records). Then purge all soft deleted records in glance tables in the
1329 # right order to avoid FK constraint violation.
1330 t = Table("tasks", metadata, autoload=True)
1331 ti = Table("task_info", metadata, autoload=True)
1332 joined_rec = ti.join(t, t.c.id == ti.c.task_id)
1333 deleted_task_info = sql.select([ti.c.task_id],
1334 t.c.deleted_at < deleted_age).\
1335 select_from(joined_rec).order_by(t.c.deleted_at).limit(max_rows)
1336 delete_statement = DeleteFromSelect(ti, deleted_task_info,
1337 ti.c.task_id)
1338 LOG.info(_LI('Purging deleted rows older than %(age_in_days)d day(s) '
1339 'from table %(tbl)s'),
1340 {'age_in_days': age_in_days, 'tbl': ti})
1341 try:
1342 with session.begin():
1343 result = session.execute(delete_statement)
1344 except (db_exception.DBError, db_exception.DBReferenceError) as ex:
1345 LOG.exception(_LE('DBError detected when force purging '
1346 'table=%(table)s: %(error)s'),
1347 {'table': ti, 'error': six.text_type(ex)})
1348 raise
1349
1350 rows = result.rowcount
1351 LOG.info(_LI('Deleted %(rows)d row(s) from table %(tbl)s'),
1352 {'rows': rows, 'tbl': ti})
1353
1325 # get rid of FK constraints 1354 # get rid of FK constraints
1326 for tbl in ('images', 'tasks'): 1355 for tbl in ('images', 'tasks'):
1327 try: 1356 try:
diff --git a/glance/domain/__init__.py b/glance/domain/__init__.py
index 2d5b7af..0bb6a29 100644
--- a/glance/domain/__init__.py
+++ b/glance/domain/__init__.py
@@ -300,9 +300,9 @@ class ExtraProperties(collections.MutableMapping, dict):
300 300
301 def __eq__(self, other): 301 def __eq__(self, other):
302 if isinstance(other, ExtraProperties): 302 if isinstance(other, ExtraProperties):
303 return dict(self).__eq__(dict(other)) 303 return dict.__eq__(self, dict(other))
304 elif isinstance(other, dict): 304 elif isinstance(other, dict):
305 return dict(self).__eq__(other) 305 return dict.__eq__(self, other)
306 else: 306 else:
307 return False 307 return False
308 308
@@ -310,10 +310,10 @@ class ExtraProperties(collections.MutableMapping, dict):
310 return not self.__eq__(other) 310 return not self.__eq__(other)
311 311
312 def __len__(self): 312 def __len__(self):
313 return dict(self).__len__() 313 return dict.__len__(self)
314 314
315 def keys(self): 315 def keys(self):
316 return dict(self).keys() 316 return dict.keys(self)
317 317
318 318
319class ImageMembership(object): 319class ImageMembership(object):
@@ -513,7 +513,7 @@ class TaskExecutorFactory(object):
513 TaskExecutorFactory.eventlet_deprecation_warned = True 513 TaskExecutorFactory.eventlet_deprecation_warned = True
514 task_executor = 'taskflow' 514 task_executor = 'taskflow'
515 515
516 executor_cls = ('glance.async.%s_executor.' 516 executor_cls = ('glance.async_.%s_executor.'
517 'TaskExecutor' % task_executor) 517 'TaskExecutor' % task_executor)
518 LOG.debug("Loading %s executor", task_executor) 518 LOG.debug("Loading %s executor", task_executor)
519 executor = importutils.import_class(executor_cls) 519 executor = importutils.import_class(executor_cls)
diff --git a/glance/locale/en_GB/LC_MESSAGES/glance.po b/glance/locale/en_GB/LC_MESSAGES/glance.po
index b579425..8dc94a4 100644
--- a/glance/locale/en_GB/LC_MESSAGES/glance.po
+++ b/glance/locale/en_GB/LC_MESSAGES/glance.po
@@ -12,11 +12,11 @@ msgid ""
12msgstr "" 12msgstr ""
13"Project-Id-Version: glance VERSION\n" 13"Project-Id-Version: glance VERSION\n"
14"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" 14"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
15"POT-Creation-Date: 2018-08-09 04:23+0000\n" 15"POT-Creation-Date: 2018-11-20 06:34+0000\n"
16"MIME-Version: 1.0\n" 16"MIME-Version: 1.0\n"
17"Content-Type: text/plain; charset=UTF-8\n" 17"Content-Type: text/plain; charset=UTF-8\n"
18"Content-Transfer-Encoding: 8bit\n" 18"Content-Transfer-Encoding: 8bit\n"
19"PO-Revision-Date: 2018-08-08 09:19+0000\n" 19"PO-Revision-Date: 2018-12-04 05:26+0000\n"
20"Last-Translator: Andi Chandler <andi@gowling.com>\n" 20"Last-Translator: Andi Chandler <andi@gowling.com>\n"
21"Language: en_GB\n" 21"Language: en_GB\n"
22"Plural-Forms: nplurals=2; plural=(n != 1);\n" 22"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -3428,6 +3428,10 @@ msgid "%s can't contain 4 byte unicode characters."
3428msgstr "%s can't contain 4 byte unicode characters." 3428msgstr "%s can't contain 4 byte unicode characters."
3429 3429
3430#, python-format 3430#, python-format
3431msgid "%s is already set with a different value"
3432msgstr "%s is already set with a different value"
3433
3434#, python-format
3431msgid "%s is already stopped" 3435msgid "%s is already stopped"
3432msgstr "%s is already stopped" 3436msgstr "%s is already stopped"
3433 3437
@@ -3660,6 +3664,10 @@ msgid "Configuration option was not valid"
3660msgstr "Configuration option was not valid" 3664msgstr "Configuration option was not valid"
3661 3665
3662#, python-format 3666#, python-format
3667msgid "Conflicting values for %s"
3668msgstr "Conflicting values for %s"
3669
3670#, python-format
3663msgid "Connect error/bad request to Auth service at URL %(url)s." 3671msgid "Connect error/bad request to Auth service at URL %(url)s."
3664msgstr "Connect error/bad request to Auth service at URL %(url)s." 3672msgstr "Connect error/bad request to Auth service at URL %(url)s."
3665 3673
@@ -4484,6 +4492,10 @@ msgstr ""
4484msgid "Must supply a non-negative value for age." 4492msgid "Must supply a non-negative value for age."
4485msgstr "Must supply a non-negative value for age." 4493msgstr "Must supply a non-negative value for age."
4486 4494
4495#, python-format
4496msgid "New value(s) for %s may only be provided when image status is 'queued'"
4497msgstr "New value(s) for %s may only be provided when image status is 'queued'"
4498
4487msgid "No authenticated user" 4499msgid "No authenticated user"
4488msgstr "No authenticated user" 4500msgstr "No authenticated user"
4489 4501
@@ -5416,6 +5428,15 @@ msgstr "Uploading the image failed due to: %(exc)s"
5416msgid "Use the http_proxy_to_wsgi middleware instead." 5428msgid "Use the http_proxy_to_wsgi middleware instead."
5417msgstr "Use the http_proxy_to_wsgi middleware instead." 5429msgstr "Use the http_proxy_to_wsgi middleware instead."
5418 5430
5431msgid ""
5432"Values to be used to populate the corresponding image properties. If the "
5433"image status is not 'queued', values must exactly match those already "
5434"contained in the image properties."
5435msgstr ""
5436"Values to be used to populate the corresponding image properties. If the "
5437"image status is not 'queued', values must exactly match those already "
5438"contained in the image properties."
5439
5419msgid "Virtual size of image in bytes" 5440msgid "Virtual size of image in bytes"
5420msgstr "Virtual size of image in bytes" 5441msgstr "Virtual size of image in bytes"
5421 5442
@@ -5644,6 +5665,14 @@ msgstr ""
5644"%(from)s)" 5665"%(from)s)"
5645 5666
5646#, python-format 5667#, python-format
5668msgid "checksum (%s) is not a valid hexadecimal value"
5669msgstr "checksum (%s) is not a valid hexadecimal value"
5670
5671#, python-format
5672msgid "checksum (%s) is not the correct size for md5 (should be 16 bytes)"
5673msgstr "checksum (%s) is not the correct size for MD5 (should be 16 bytes)"
5674
5675#, python-format
5647msgid "custom properties (%(props)s) conflict with base properties" 5676msgid "custom properties (%(props)s) conflict with base properties"
5648msgstr "custom properties (%(props)s) conflict with base properties" 5677msgstr "custom properties (%(props)s) conflict with base properties"
5649 5678
@@ -5666,6 +5695,22 @@ msgstr "MD5 hash of image contents."
5666msgid "new_image() got unexpected keywords %s" 5695msgid "new_image() got unexpected keywords %s"
5667msgstr "new_image() got unexpected keywords %s" 5696msgstr "new_image() got unexpected keywords %s"
5668 5697
5698#, python-format
5699msgid "os_hash_algo must be %(want)s, not %(got)s"
5700msgstr "os_hash_algo must be %(want)s, not %(got)s"
5701
5702#, python-format
5703msgid ""
5704"os_hash_value (%(value)s) is not the correct size for %(algo)s (should be "
5705"%(want)d bytes)"
5706msgstr ""
5707"os_hash_value (%(value)s) is not the correct size for %(algo)s (should be "
5708"%(want)d bytes)"
5709
5710#, python-format
5711msgid "os_hash_value (%s) is not a valid hexadecimal value"
5712msgstr "os_hash_value (%s) is not a valid hexadecimal value"
5713
5669msgid "protected must be True, or False" 5714msgid "protected must be True, or False"
5670msgstr "protected must be True, or False" 5715msgstr "protected must be True, or False"
5671 5716
diff --git a/glance/notifier.py b/glance/notifier.py
index 8db5efc..1d16495 100644
--- a/glance/notifier.py
+++ b/glance/notifier.py
@@ -448,7 +448,7 @@ class ImageProxy(NotificationProxy, domain_proxy.Image):
448 raise webob.exc.HTTPBadRequest( 448 raise webob.exc.HTTPBadRequest(
449 explanation=encodeutils.exception_to_unicode(e)) 449 explanation=encodeutils.exception_to_unicode(e))
450 except exception.Duplicate as e: 450 except exception.Duplicate as e:
451 msg = (_("Unable to upload duplicate image data for image" 451 msg = (_("Unable to upload duplicate image data for image "
452 "%(image_id)s: %(error)s") % 452 "%(image_id)s: %(error)s") %
453 {'image_id': self.repo.image_id, 453 {'image_id': self.repo.image_id,
454 'error': encodeutils.exception_to_unicode(e)}) 454 'error': encodeutils.exception_to_unicode(e)})
diff --git a/glance/opts.py b/glance/opts.py
index 93caf40..9a7fbf0 100644
--- a/glance/opts.py
+++ b/glance/opts.py
@@ -28,11 +28,11 @@ from osprofiler import opts as profiler
28 28
29import glance.api.middleware.context 29import glance.api.middleware.context
30import glance.api.versions 30import glance.api.versions
31import glance.async.flows._internal_plugins 31import glance.async_.flows._internal_plugins
32import glance.async.flows.api_image_import 32import glance.async_.flows.api_image_import
33import glance.async.flows.convert 33import glance.async_.flows.convert
34from glance.async.flows.plugins import plugin_opts 34from glance.async_.flows.plugins import plugin_opts
35import glance.async.taskflow_executor 35import glance.async_.taskflow_executor
36import glance.common.config 36import glance.common.config
37import glance.common.location_strategy 37import glance.common.location_strategy
38import glance.common.location_strategy.store_type 38import glance.common.location_strategy.store_type
@@ -60,6 +60,7 @@ _api_opts = [
60 glance.common.wsgi.eventlet_opts, 60 glance.common.wsgi.eventlet_opts,
61 glance.common.wsgi.socket_opts, 61 glance.common.wsgi.socket_opts,
62 glance.common.wsgi.wsgi_opts, 62 glance.common.wsgi.wsgi_opts,
63 glance.common.wsgi.store_opts,
63 glance.image_cache.drivers.sqlite.sqlite_opts, 64 glance.image_cache.drivers.sqlite.sqlite_opts,
64 glance.image_cache.image_cache_opts, 65 glance.image_cache.image_cache_opts,
65 glance.notifier.notifier_opts, 66 glance.notifier.notifier_opts,
@@ -71,8 +72,8 @@ _api_opts = [
71 ('image_format', glance.common.config.image_format_opts), 72 ('image_format', glance.common.config.image_format_opts),
72 ('task', glance.common.config.task_opts), 73 ('task', glance.common.config.task_opts),
73 ('taskflow_executor', list(itertools.chain( 74 ('taskflow_executor', list(itertools.chain(
74 glance.async.taskflow_executor.taskflow_executor_opts, 75 glance.async_.taskflow_executor.taskflow_executor_opts,
75 glance.async.flows.convert.convert_task_opts))), 76 glance.async_.flows.convert.convert_task_opts))),
76 ('store_type_location_strategy', 77 ('store_type_location_strategy',
77 glance.common.location_strategy.store_type.store_type_opts), 78 glance.common.location_strategy.store_type.store_type_opts),
78 profiler.list_opts()[0], 79 profiler.list_opts()[0],
@@ -109,9 +110,10 @@ _manage_opts = [
109 (None, []) 110 (None, [])
110] 111]
111_image_import_opts = [ 112_image_import_opts = [
112 ('image_import_opts', glance.async.flows.api_image_import.api_import_opts), 113 ('image_import_opts',
114 glance.async_.flows.api_image_import.api_import_opts),
113 ('import_filtering_opts', 115 ('import_filtering_opts',
114 glance.async.flows._internal_plugins.import_filtering_opts), 116 glance.async_.flows._internal_plugins.import_filtering_opts),
115] 117]
116 118
117 119
diff --git a/glance/quota/__init__.py b/glance/quota/__init__.py
index 1d721c5..32ba983 100644
--- a/glance/quota/__init__.py
+++ b/glance/quota/__init__.py
@@ -165,6 +165,14 @@ class QuotaImageTagsProxy(object):
165 return self.tags.__len__(*args, **kwargs) 165 return self.tags.__len__(*args, **kwargs)
166 166
167 def __getattr__(self, name): 167 def __getattr__(self, name):
168 # Protect against deepcopy, which calls getattr. __getattr__
169 # is only called when an attribute is not "normal", so when
170 # self.tags is called, this is not.
171 if name == 'tags':
172 try:
173 return self.__getattribute__('tags')
174 except AttributeError:
175 return None
168 return getattr(self.tags, name) 176 return getattr(self.tags, name)
169 177
170 178
diff --git a/glance/tests/functional/__init__.py b/glance/tests/functional/__init__.py
index d6ac835..ac42b1d 100644
--- a/glance/tests/functional/__init__.py
+++ b/glance/tests/functional/__init__.py
@@ -364,6 +364,8 @@ store_type_preference = %(store_type_location_strategy_preference)s
364[glance_store] 364[glance_store]
365filesystem_store_datadir=%(image_dir)s 365filesystem_store_datadir=%(image_dir)s
366default_store = %(default_store)s 366default_store = %(default_store)s
367[import_filtering_opts]
368allowed_ports = []
367""" 369"""
368 self.paste_conf_base = """[pipeline:glance-api] 370 self.paste_conf_base = """[pipeline:glance-api]
369pipeline = 371pipeline =
@@ -549,6 +551,8 @@ default_backend = %(default_backend)s
549filesystem_store_datadir=%(image_dir_backend_1)s 551filesystem_store_datadir=%(image_dir_backend_1)s
550[file2] 552[file2]
551filesystem_store_datadir=%(image_dir_backend_2)s 553filesystem_store_datadir=%(image_dir_backend_2)s
554[import_filtering_opts]
555allowed_ports = []
552""" 556"""
553 self.paste_conf_base = """[pipeline:glance-api] 557 self.paste_conf_base = """[pipeline:glance-api]
554pipeline = 558pipeline =
diff --git a/glance/tests/functional/db/base.py b/glance/tests/functional/db/base.py
index 0b08f83..6d8a4dd 100644
--- a/glance/tests/functional/db/base.py
+++ b/glance/tests/functional/db/base.py
@@ -1946,6 +1946,7 @@ class DBPurgeTests(test_utils.BaseTestCase):
1946 self.adm_context = context.get_admin_context(show_deleted=True) 1946 self.adm_context = context.get_admin_context(show_deleted=True)
1947 self.db_api = db_tests.get_db(self.config) 1947 self.db_api = db_tests.get_db(self.config)
1948 db_tests.reset_db(self.db_api) 1948 db_tests.reset_db(self.db_api)
1949 self.context = context.RequestContext(is_admin=True)
1949 self.image_fixtures, self.task_fixtures = self.build_fixtures() 1950 self.image_fixtures, self.task_fixtures = self.build_fixtures()
1950 self.create_tasks(self.task_fixtures) 1951 self.create_tasks(self.task_fixtures)
1951 self.create_images(self.image_fixtures) 1952 self.create_images(self.image_fixtures)
@@ -2079,6 +2080,29 @@ class DBPurgeTests(test_utils.BaseTestCase):
2079 images_rows = session.query(images).count() 2080 images_rows = session.query(images).count()
2080 self.assertEqual(4, images_rows) 2081 self.assertEqual(4, images_rows)
2081 2082
2083 def test_purge_task_info_with_refs_to_soft_deleted_tasks(self):
2084 session = db_api.get_session()
2085 engine = db_api.get_engine()
2086
2087 # check initial task and task_info row number are 3
2088 tasks = self.db_api.task_get_all(self.adm_context)
2089 self.assertEqual(3, len(tasks))
2090
2091 task_info = sqlalchemyutils.get_table(engine, 'task_info')
2092 task_info_rows = session.query(task_info).count()
2093 self.assertEqual(3, task_info_rows)
2094
2095 # purge soft deleted rows older than yesterday
2096 self.db_api.purge_deleted_rows(self.context, 1, 5)
2097
2098 # check 1 row of task table is purged
2099 tasks = self.db_api.task_get_all(self.adm_context)
2100 self.assertEqual(2, len(tasks))
2101
2102 # and no task_info was left behind, 1 row purged
2103 task_info_rows = session.query(task_info).count()
2104 self.assertEqual(2, task_info_rows)
2105
2082 2106
2083class TestVisibility(test_utils.BaseTestCase): 2107class TestVisibility(test_utils.BaseTestCase):
2084 def setUp(self): 2108 def setUp(self):
diff --git a/glance/tests/functional/v2/test_images.py b/glance/tests/functional/v2/test_images.py
index eb58436..7d6d933 100644
--- a/glance/tests/functional/v2/test_images.py
+++ b/glance/tests/functional/v2/test_images.py
@@ -341,8 +341,11 @@ class TestImages(functional.FunctionalTest):
341 'content-type': 'application/json', 341 'content-type': 'application/json',
342 'X-Roles': 'admin', 342 'X-Roles': 'admin',
343 }) 343 })
344 image_data_uri = ('https://www.openstack.org/assets/openstack-logo/' 344
345 '2016R/OpenStack-Logo-Horizontal.eps.zip') 345 # Start http server locally
346 pid, port = test_utils.start_standalone_http_server()
347
348 image_data_uri = 'http://localhost:%s/' % port
346 data = jsonutils.dumps({'method': { 349 data = jsonutils.dumps({'method': {
347 'name': 'web-download', 350 'name': 'web-download',
348 'uri': image_data_uri 351 'uri': image_data_uri
@@ -369,6 +372,9 @@ class TestImages(functional.FunctionalTest):
369 os_hash_value=expect_h, 372 os_hash_value=expect_h,
370 status='active') 373 status='active')
371 374
375 # kill the local http server
376 os.kill(pid, signal.SIGKILL)
377
372 # Deleting image should work 378 # Deleting image should work
373 path = self._url('/v2/images/%s' % image_id) 379 path = self._url('/v2/images/%s' % image_id)
374 response = requests.delete(path, headers=self._headers()) 380 response = requests.delete(path, headers=self._headers())
@@ -4901,8 +4907,11 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
4901 'content-type': 'application/json', 4907 'content-type': 'application/json',
4902 'X-Roles': 'admin', 4908 'X-Roles': 'admin',
4903 }) 4909 })
4904 image_data_uri = ('https://www.openstack.org/assets/openstack-logo/' 4910
4905 '2016R/OpenStack-Logo-Horizontal.eps.zip') 4911 # Start http server locally
4912 pid, port = test_utils.start_standalone_http_server()
4913
4914 image_data_uri = 'http://localhost:%s/' % port
4906 data = jsonutils.dumps({'method': { 4915 data = jsonutils.dumps({'method': {
4907 'name': 'web-download', 4916 'name': 'web-download',
4908 'uri': image_data_uri 4917 'uri': image_data_uri
@@ -4928,6 +4937,9 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
4928 checksum=expect_c, 4937 checksum=expect_c,
4929 os_hash_value=expect_h, 4938 os_hash_value=expect_h,
4930 status='active') 4939 status='active')
4940
4941 # kill the local http server
4942 os.kill(pid, signal.SIGKILL)
4931 # Ensure image is created in default backend 4943 # Ensure image is created in default backend
4932 path = self._url('/v2/images/%s' % image_id) 4944 path = self._url('/v2/images/%s' % image_id)
4933 response = requests.get(path, headers=self._headers()) 4945 response = requests.get(path, headers=self._headers())
@@ -5055,8 +5067,11 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
5055 'X-Roles': 'admin', 5067 'X-Roles': 'admin',
5056 'X-Image-Meta-Store': 'file2' 5068 'X-Image-Meta-Store': 'file2'
5057 }) 5069 })
5058 image_data_uri = ('https://www.openstack.org/assets/openstack-logo/' 5070
5059 '2016R/OpenStack-Logo-Horizontal.eps.zip') 5071 # Start http server locally
5072 pid, port = test_utils.start_standalone_http_server()
5073
5074 image_data_uri = 'http://localhost:%s/' % port
5060 data = jsonutils.dumps({'method': { 5075 data = jsonutils.dumps({'method': {
5061 'name': 'web-download', 5076 'name': 'web-download',
5062 'uri': image_data_uri 5077 'uri': image_data_uri
@@ -5082,6 +5097,10 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
5082 checksum=expect_c, 5097 checksum=expect_c,
5083 os_hash_value=expect_h, 5098 os_hash_value=expect_h,
5084 status='active') 5099 status='active')
5100
5101 # kill the local http server
5102 os.kill(pid, signal.SIGKILL)
5103
5085 # Ensure image is created in different backend 5104 # Ensure image is created in different backend
5086 path = self._url('/v2/images/%s' % image_id) 5105 path = self._url('/v2/images/%s' % image_id)
5087 response = requests.get(path, headers=self._headers()) 5106 response = requests.get(path, headers=self._headers())
diff --git a/glance/tests/stubs.py b/glance/tests/stubs.py
index 80bf5c8..583ad56 100644
--- a/glance/tests/stubs.py
+++ b/glance/tests/stubs.py
@@ -161,19 +161,6 @@ def stub_out_registry_and_store_server(stubs, base_dir, **kwargs):
161 setattr(res, 'read', fake_reader) 161 setattr(res, 'read', fake_reader)
162 return res 162 return res
163 163
164 def fake_get_connection_type(client):
165 """Returns the proper connection type."""
166 DEFAULT_REGISTRY_PORT = 9191
167 DEFAULT_API_PORT = 9292
168
169 if (client.port == DEFAULT_API_PORT and
170 client.host == '0.0.0.0'):
171 return FakeGlanceConnection
172 elif (client.port == DEFAULT_REGISTRY_PORT and
173 client.host == '0.0.0.0'):
174 rserver = kwargs.get("registry")
175 return FakeRegistryConnection(registry=rserver)
176
177 def fake_image_iter(self): 164 def fake_image_iter(self):
178 for i in self.source.app_iter: 165 for i in self.source.app_iter:
179 yield i 166 yield i
@@ -187,32 +174,5 @@ def stub_out_registry_and_store_server(stubs, base_dir, **kwargs):
187 assert glance.common.client.SENDFILE_SUPPORTED 174 assert glance.common.client.SENDFILE_SUPPORTED
188 return force 175 return force
189 176
190 stubs.Set(glance.common.client.BaseClient, 'get_connection_type',
191 fake_get_connection_type)
192 setattr(glance.common.client.BaseClient, '_stub_orig_sendable', 177 setattr(glance.common.client.BaseClient, '_stub_orig_sendable',
193 glance.common.client.BaseClient._sendable) 178 glance.common.client.BaseClient._sendable)
194 stubs.Set(glance.common.client.BaseClient, '_sendable',
195 fake_sendable)
196
197
198def stub_out_registry_server(stubs, **kwargs):
199 """Mocks calls to 127.0.0.1 on 9191 for testing.
200
201 Done so that a real Glance Registry server does not need to be up and
202 running.
203 """
204 def fake_get_connection_type(client):
205 """Returns the proper connection type."""
206 DEFAULT_REGISTRY_PORT = 9191
207
208 if (client.port == DEFAULT_REGISTRY_PORT and
209 client.host == '0.0.0.0'):
210 rserver = kwargs.pop("registry", None)
211 return FakeRegistryConnection(registry=rserver)
212
213 def fake_image_iter(self):
214 for i in self.response.app_iter:
215 yield i
216
217 stubs.Set(glance.common.client.BaseClient, 'get_connection_type',
218 fake_get_connection_type)
diff --git a/glance/tests/unit/api/test_common.py b/glance/tests/unit/api/test_common.py
index ab1ccb9..cf3c9f9 100644
--- a/glance/tests/unit/api/test_common.py
+++ b/glance/tests/unit/api/test_common.py
@@ -34,7 +34,7 @@ class SimpleIterator(object):
34 yield chunk 34 yield chunk
35 chunk = read_chunk() 35 chunk = read_chunk()
36 else: 36 else:
37 raise StopIteration() 37 return
38 38
39 39
40class TestSizeCheckedIter(testtools.TestCase): 40class TestSizeCheckedIter(testtools.TestCase):
diff --git a/glance/tests/unit/async/__init__.py b/glance/tests/unit/async_/__init__.py
index e69de29..e69de29 100644
--- a/glance/tests/unit/async/__init__.py
+++ b/glance/tests/unit/async_/__init__.py
diff --git a/glance/tests/unit/async/flows/__init__.py b/glance/tests/unit/async_/flows/__init__.py
index e69de29..e69de29 100644
--- a/glance/tests/unit/async/flows/__init__.py
+++ b/glance/tests/unit/async_/flows/__init__.py
diff --git a/glance/tests/unit/async/flows/plugins/__init__.py b/glance/tests/unit/async_/flows/plugins/__init__.py
index e69de29..e69de29 100644
--- a/glance/tests/unit/async/flows/plugins/__init__.py
+++ b/glance/tests/unit/async_/flows/plugins/__init__.py
diff --git a/glance/tests/unit/async_/flows/plugins/test_image_conversion.py b/glance/tests/unit/async_/flows/plugins/test_image_conversion.py
new file mode 100644
index 0000000..df6e420
--- /dev/null
+++ b/glance/tests/unit/async_/flows/plugins/test_image_conversion.py
@@ -0,0 +1,124 @@
1# Copyright 2018 RedHat, Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16import json
17import mock
18import os
19
20import glance_store
21from oslo_concurrency import processutils
22from oslo_config import cfg
23
24import glance.async_.flows.plugins.image_conversion as image_conversion
25from glance.common import utils
26from glance import domain
27from glance import gateway
28import glance.tests.utils as test_utils
29
30CONF = cfg.CONF
31
32
33UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
34TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
35
36
37class TestConvertImageTask(test_utils.BaseTestCase):
38
39 def setUp(self):
40 super(TestConvertImageTask, self).setUp()
41
42 glance_store.register_opts(CONF)
43 self.config(default_store='file',
44 stores=['file', 'http'],
45 filesystem_store_datadir=self.test_dir,
46 group="glance_store")
47 self.config(output_format='qcow2',
48 group='image_conversion')
49 glance_store.create_stores(CONF)
50
51 self.work_dir = os.path.join(self.test_dir, 'work_dir')
52 utils.safe_mkdirs(self.work_dir)
53 self.config(work_dir=self.work_dir, group='task')
54
55 self.context = mock.MagicMock()
56 self.img_repo = mock.MagicMock()
57 self.task_repo = mock.MagicMock()
58 self.image_id = UUID1
59
60 self.gateway = gateway.Gateway()
61 self.task_factory = domain.TaskFactory()
62 self.img_factory = self.gateway.get_image_factory(self.context)
63 self.image = self.img_factory.new_image(image_id=self.image_id,
64 disk_format='raw',
65 container_format='bare')
66
67 task_input = {
68 "import_from": "http://cloud.foo/image.raw",
69 "import_from_format": "raw",
70 "image_properties": {'disk_format': 'raw',
71 'container_format': 'bare'}
72 }
73
74 task_ttl = CONF.task.task_time_to_live
75
76 self.task_type = 'import'
77 self.task = self.task_factory.new_task(self.task_type, TENANT1,
78 task_time_to_live=task_ttl,
79 task_input=task_input)
80
81 @mock.patch.object(os, 'remove')
82 def test_image_convert_success(self, mock_os_remove):
83 mock_os_remove.return_value = None
84 image_convert = image_conversion._ConvertImage(self.context,
85 self.task.task_id,
86 self.task_type,
87 self.img_repo,
88 self.image_id)
89
90 self.task_repo.get.return_value = self.task
91 image = mock.MagicMock(image_id=self.image_id, virtual_size=None,
92 disk_format='qcow2')
93 self.img_repo.get.return_value = image
94
95 with mock.patch.object(processutils, 'execute') as exc_mock:
96 exc_mock.return_value = ("", None)
97 with mock.patch.object(json, 'loads') as jloads_mock:
98 jloads_mock.return_value = {'format': 'raw'}
99 image_convert.execute('file:///test/path.raw')
100
101 # NOTE(hemanthm): Asserting that the source format is passed
102 # to qemu-utis to avoid inferring the image format. This
103 # shields us from an attack vector described at
104 # https://bugs.launchpad.net/glance/+bug/1449062/comments/72
105 self.assertIn('-f', exc_mock.call_args[0])
106 self.assertEqual("qcow2", image.disk_format)
107
108 @mock.patch.object(os, 'remove')
109 def test_image_convert_revert_success(self, mock_os_remove):
110 mock_os_remove.return_value = None
111 image_convert = image_conversion._ConvertImage(self.context,
112 self.task.task_id,
113 self.task_type,
114 self.img_repo,
115 self.image_id)
116
117 self.task_repo.get.return_value = self.task
118
119 with mock.patch.object(processutils, 'execute') as exc_mock:
120 exc_mock.return_value = ("", None)
121 with mock.patch.object(os.path, 'exists') as os_exists_mock:
122 os_exists_mock.return_value = True
123 image_convert.revert(result=mock.MagicMock())
124 self.assertEqual(1, mock_os_remove.call_count)
diff --git a/glance/tests/unit/async/flows/plugins/test_inject_image_metadata.py b/glance/tests/unit/async_/flows/plugins/test_inject_image_metadata.py
index 8234607..8bc7c11 100644
--- a/glance/tests/unit/async/flows/plugins/test_inject_image_metadata.py
+++ b/glance/tests/unit/async_/flows/plugins/test_inject_image_metadata.py
@@ -19,7 +19,7 @@ import os
19import glance_store 19import glance_store
20from oslo_config import cfg 20from oslo_config import cfg
21 21
22import glance.async.flows.plugins.inject_image_metadata as inject_metadata 22import glance.async_.flows.plugins.inject_image_metadata as inject_metadata
23from glance.common import utils 23from glance.common import utils
24from glance import domain 24from glance import domain
25from glance import gateway 25from glance import gateway
diff --git a/glance/tests/unit/async/flows/test_api_image_import.py b/glance/tests/unit/async_/flows/test_api_image_import.py
index 20aa6da..5519027 100644
--- a/glance/tests/unit/async/flows/test_api_image_import.py
+++ b/glance/tests/unit/async_/flows/test_api_image_import.py
@@ -17,7 +17,7 @@ import mock
17 17
18from oslo_config import cfg 18from oslo_config import cfg
19 19
20import glance.async.flows.api_image_import as import_flow 20import glance.async_.flows.api_image_import as import_flow
21import glance.tests.utils as test_utils 21import glance.tests.utils as test_utils
22 22
23CONF = cfg.CONF 23CONF = cfg.CONF
@@ -52,7 +52,7 @@ class TestApiImageImportTask(test_utils.BaseTestCase):
52 self.mock_task_repo = mock.MagicMock() 52 self.mock_task_repo = mock.MagicMock()
53 self.mock_image_repo = mock.MagicMock() 53 self.mock_image_repo = mock.MagicMock()
54 54
55 @mock.patch('glance.async.flows.api_image_import._VerifyStaging.__init__') 55 @mock.patch('glance.async_.flows.api_image_import._VerifyStaging.__init__')
56 @mock.patch('taskflow.patterns.linear_flow.Flow.add') 56 @mock.patch('taskflow.patterns.linear_flow.Flow.add')
57 @mock.patch('taskflow.patterns.linear_flow.__init__') 57 @mock.patch('taskflow.patterns.linear_flow.__init__')
58 def _pass_uri(self, mock_lf_init, mock_flow_add, mock_VS_init, 58 def _pass_uri(self, mock_lf_init, mock_flow_add, mock_VS_init,
diff --git a/glance/tests/unit/async/flows/test_convert.py b/glance/tests/unit/async_/flows/test_convert.py
index b3c3848..8b141e2 100644
--- a/glance/tests/unit/async/flows/test_convert.py
+++ b/glance/tests/unit/async_/flows/test_convert.py
@@ -22,8 +22,8 @@ from oslo_concurrency import processutils
22from oslo_config import cfg 22from oslo_config import cfg
23import six 23import six
24 24
25from glance.async.flows import convert 25from glance.async_.flows import convert
26from glance.async import taskflow_executor 26from glance.async_ import taskflow_executor
27from glance.common.scripts import utils as script_utils 27from glance.common.scripts import utils as script_utils
28from glance.common import utils 28from glance.common import utils
29from glance import domain 29from glance import domain
diff --git a/glance/tests/unit/async/flows/test_import.py b/glance/tests/unit/async_/flows/test_import.py
index 70752d8..e3d1a4a 100644
--- a/glance/tests/unit/async/flows/test_import.py
+++ b/glance/tests/unit/async_/flows/test_import.py
@@ -25,9 +25,9 @@ from six.moves import urllib
25from taskflow import task 25from taskflow import task
26from taskflow.types import failure 26from taskflow.types import failure
27 27
28import glance.async.flows.base_import as import_flow 28import glance.async_.flows.base_import as import_flow
29from glance.async import taskflow_executor 29from glance.async_ import taskflow_executor
30from glance.async import utils as async_utils 30from glance.async_ import utils as async_utils
31from glance.common.scripts.image_import import main as image_import 31from glance.common.scripts.image_import import main as image_import
32from glance.common.scripts import utils as script_utils 32from glance.common.scripts import utils as script_utils
33from glance.common import utils 33from glance.common import utils
diff --git a/glance/tests/unit/async/flows/test_introspect.py b/glance/tests/unit/async_/flows/test_introspect.py
index 869f99d..573c441 100644
--- a/glance/tests/unit/async/flows/test_introspect.py
+++ b/glance/tests/unit/async_/flows/test_introspect.py
@@ -20,8 +20,8 @@ import glance_store
20from oslo_concurrency import processutils 20from oslo_concurrency import processutils
21from oslo_config import cfg 21from oslo_config import cfg
22 22
23from glance.async.flows import introspect 23from glance.async_.flows import introspect
24from glance.async import utils as async_utils 24from glance.async_ import utils as async_utils
25from glance import domain 25from glance import domain
26import glance.tests.utils as test_utils 26import glance.tests.utils as test_utils
27 27
diff --git a/glance/tests/unit/async/flows/test_ovf_process.py b/glance/tests/unit/async_/flows/test_ovf_process.py
index 5646cae..0a70bc2 100644
--- a/glance/tests/unit/async/flows/test_ovf_process.py
+++ b/glance/tests/unit/async_/flows/test_ovf_process.py
@@ -24,7 +24,7 @@ try:
24except ImportError: 24except ImportError:
25 from defusedxml.ElementTree import ParseError 25 from defusedxml.ElementTree import ParseError
26 26
27from glance.async.flows import ovf_process 27from glance.async_.flows import ovf_process
28import glance.tests.utils as test_utils 28import glance.tests.utils as test_utils
29from oslo_config import cfg 29from oslo_config import cfg
30 30
diff --git a/glance/tests/unit/async/flows/test_web_download.py b/glance/tests/unit/async_/flows/test_web_download.py
index ca62f0a..5384320 100644
--- a/glance/tests/unit/async/flows/test_web_download.py
+++ b/glance/tests/unit/async_/flows/test_web_download.py
@@ -19,7 +19,7 @@ from glance_store._drivers import filesystem
19from glance_store import backend 19from glance_store import backend
20from oslo_config import cfg 20from oslo_config import cfg
21 21
22from glance.async.flows._internal_plugins import web_download 22from glance.async_.flows._internal_plugins import web_download
23 23
24import glance.common.exception 24import glance.common.exception
25import glance.common.scripts.utils as script_utils 25import glance.common.scripts.utils as script_utils
diff --git a/glance/tests/unit/async/test_async.py b/glance/tests/unit/async_/test_async.py
index 8efa48a..d6a1212 100644
--- a/glance/tests/unit/async/test_async.py
+++ b/glance/tests/unit/async_/test_async.py
@@ -16,7 +16,7 @@
16 16
17import mock 17import mock
18 18
19import glance.async 19import glance.async_
20import glance.tests.utils as test_utils 20import glance.tests.utils as test_utils
21 21
22 22
@@ -28,10 +28,10 @@ class TestTaskExecutor(test_utils.BaseTestCase):
28 self.task_repo = mock.Mock() 28 self.task_repo = mock.Mock()
29 self.image_repo = mock.Mock() 29 self.image_repo = mock.Mock()
30 self.image_factory = mock.Mock() 30 self.image_factory = mock.Mock()
31 self.executor = glance.async.TaskExecutor(self.context, 31 self.executor = glance.async_.TaskExecutor(self.context,
32 self.task_repo, 32 self.task_repo,
33 self.image_repo, 33 self.image_repo,
34 self.image_factory) 34 self.image_factory)
35 35
36 def test_begin_processing(self): 36 def test_begin_processing(self):
37 # setup 37 # setup
@@ -40,7 +40,7 @@ class TestTaskExecutor(test_utils.BaseTestCase):
40 task = mock.Mock() 40 task = mock.Mock()
41 41
42 with mock.patch.object( 42 with mock.patch.object(
43 glance.async.TaskExecutor, 43 glance.async_.TaskExecutor,
44 '_run') as mock_run: 44 '_run') as mock_run:
45 self.task_repo.get.return_value = task 45 self.task_repo.get.return_value = task
46 self.executor.begin_processing(task_id) 46 self.executor.begin_processing(task_id)
diff --git a/glance/tests/unit/async/test_taskflow_executor.py b/glance/tests/unit/async_/test_taskflow_executor.py
index c1a4ef6..222ad4d 100644
--- a/glance/tests/unit/async/test_taskflow_executor.py
+++ b/glance/tests/unit/async_/test_taskflow_executor.py
@@ -19,7 +19,7 @@ import glance_store
19from oslo_config import cfg 19from oslo_config import cfg
20from taskflow import engines 20from taskflow import engines
21 21
22from glance.async import taskflow_executor 22from glance.async_ import taskflow_executor
23from glance.common.scripts.image_import import main as image_import 23from glance.common.scripts.image_import import main as image_import
24from glance import domain 24from glance import domain
25import glance.tests.utils as test_utils 25import glance.tests.utils as test_utils
diff --git a/glance/tests/unit/base.py b/glance/tests/unit/base.py
index 69de6fa..cc35342 100644
--- a/glance/tests/unit/base.py
+++ b/glance/tests/unit/base.py
@@ -103,9 +103,23 @@ class IsolatedUnitTest(StoreClearingUnitTest):
103 group="glance_store") 103 group="glance_store")
104 104
105 store.create_stores() 105 store.create_stores()
106 stubs.stub_out_registry_and_store_server(self.stubs, 106
107 self.test_dir, 107 def fake_get_conection_type(client):
108 registry=self.registry) 108 DEFAULT_REGISTRY_PORT = 9191
109 DEFAULT_API_PORT = 9292
110
111 if (client.port == DEFAULT_API_PORT and
112 client.host == '0.0.0.0'):
113 return stubs.FakeGlanceConnection
114 elif (client.port == DEFAULT_REGISTRY_PORT and
115 client.host == '0.0.0.0'):
116 return stubs.FakeRegistryConnection(registry=self.registry)
117
118 self.patcher = mock.patch(
119 'glance.common.client.BaseClient.get_connection_type',
120 fake_get_conection_type)
121 self.addCleanup(self.patcher.stop)
122 self.patcher.start()
109 123
110 def set_policy_rules(self, rules): 124 def set_policy_rules(self, rules):
111 fap = open(CONF.oslo_policy.policy_file, 'w') 125 fap = open(CONF.oslo_policy.policy_file, 'w')
@@ -127,7 +141,7 @@ class MultiIsolatedUnitTest(MultiStoreClearingUnitTest):
127 lockutils.set_defaults(os.path.join(self.test_dir)) 141 lockutils.set_defaults(os.path.join(self.test_dir))
128 142
129 self.config(debug=False) 143 self.config(debug=False)
130 stubs.stub_out_registry_and_store_server(self.stubs, 144 stubs.stub_out_registry_and_store_server(self,
131 self.test_dir, 145 self.test_dir,
132 registry=self.registry) 146 registry=self.registry)
133 147
diff --git a/glance/tests/unit/common/test_utils.py b/glance/tests/unit/common/test_utils.py
index 48911c6..98ff61c 100644
--- a/glance/tests/unit/common/test_utils.py
+++ b/glance/tests/unit/common/test_utils.py
@@ -87,7 +87,7 @@ class TestUtils(test_utils.BaseTestCase):
87 yield chunk 87 yield chunk
88 iteration += 1 88 iteration += 1
89 if iteration >= max_iterations: 89 if iteration >= max_iterations:
90 raise StopIteration() 90 return
91 91
92 def _test_reader_chunked(self, chunk_size, read_size, max_iterations=5): 92 def _test_reader_chunked(self, chunk_size, read_size, max_iterations=5):
93 generator = self._create_generator(chunk_size, max_iterations) 93 generator = self._create_generator(chunk_size, max_iterations)
diff --git a/glance/tests/unit/common/test_wsgi.py b/glance/tests/unit/common/test_wsgi.py
index adda23e..794873d 100644
--- a/glance/tests/unit/common/test_wsgi.py
+++ b/glance/tests/unit/common/test_wsgi.py
@@ -184,31 +184,20 @@ class RequestTest(test_utils.BaseTestCase):
184 req = wsgi.Request.blank('/', headers={'Accept-Language': 'unknown'}) 184 req = wsgi.Request.blank('/', headers={'Accept-Language': 'unknown'})
185 self.assertIsNone(req.best_match_language()) 185 self.assertIsNone(req.best_match_language())
186 186
187 def test_best_match_language_unknown(self): 187 @mock.patch.object(webob.acceptparse.AcceptLanguageValidHeader, 'lookup')
188 # Test that we are actually invoking language negotiation by webop 188 def test_best_match_language_unknown(self, mock_lookup):
189 # Test that we are actually invoking language negotiation by WebOb
189 request = wsgi.Request.blank('/') 190 request = wsgi.Request.blank('/')
190 accepted = 'unknown-lang' 191 accepted = 'unknown-lang'
191 request.headers = {'Accept-Language': accepted} 192 request.headers = {'Accept-Language': accepted}
192 193
193 # TODO(rosmaita): simplify when lower_constraints has webob >= 1.8.1 194 # Bug #1765748: see comment in code in the function under test
194 try: 195 # to understand why this is the correct return value for the
195 from webob.acceptparse import AcceptLanguageValidHeader # noqa 196 # webob 1.8.x mock
196 cls = webob.acceptparse.AcceptLanguageValidHeader 197 mock_lookup.return_value = 'fake_LANG'
197 funcname = 'lookup' 198
198 # Bug #1765748: see comment in code in the function under test 199 self.assertIsNone(request.best_match_language())
199 # to understand why this is the correct return value for the 200 mock_lookup.assert_called_once()
200 # webob 1.8.x mock
201 retval = 'fake_LANG'
202 except ImportError:
203 cls = webob.acceptparse.AcceptLanguage
204 funcname = 'best_match'
205 retval = None
206
207 with mock.patch.object(cls, funcname) as mocked_function:
208 mocked_function.return_value = retval
209
210 self.assertIsNone(request.best_match_language())
211 mocked_function.assert_called_once()
212 201
213 # If Accept-Language is missing or empty, match should be None 202 # If Accept-Language is missing or empty, match should be None
214 request.headers = {'Accept-Language': ''} 203 request.headers = {'Accept-Language': ''}
@@ -389,27 +378,18 @@ class ResourceTest(test_utils.BaseTestCase):
389 resource, request) 378 resource, request)
390 self.assertEqual(message_es, str(e)) 379 self.assertEqual(message_es, str(e))
391 380
381 @mock.patch.object(webob.acceptparse.AcceptLanguageValidHeader, 'lookup')
392 @mock.patch.object(i18n, 'translate') 382 @mock.patch.object(i18n, 'translate')
393 def test_translate_exception(self, mock_translate): 383 def test_translate_exception(self, mock_translate, mock_lookup):
394 # TODO(rosmaita): simplify when lower_constraints has webob >= 1.8.1 384 mock_translate.return_value = 'No Encontrado'
395 try: 385 mock_lookup.return_value = 'de'
396 from webob.acceptparse import AcceptLanguageValidHeader # noqa 386
397 cls = webob.acceptparse.AcceptLanguageValidHeader 387 req = wsgi.Request.blank('/tests/123')
398 funcname = 'lookup' 388 req.headers["Accept-Language"] = "de"
399 except ImportError: 389
400 cls = webob.acceptparse.AcceptLanguage 390 e = webob.exc.HTTPNotFound(explanation='Not Found')
401 funcname = 'best_match' 391 e = wsgi.translate_exception(req, e)
402 392 self.assertEqual('No Encontrado', e.explanation)
403 with mock.patch.object(cls, funcname) as mocked_function:
404 mock_translate.return_value = 'No Encontrado'
405 mocked_function.return_value = 'de'
406
407 req = wsgi.Request.blank('/tests/123')
408 req.headers["Accept-Language"] = "de"
409
410 e = webob.exc.HTTPNotFound(explanation='Not Found')
411 e = wsgi.translate_exception(req, e)
412 self.assertEqual('No Encontrado', e.explanation)
413 393
414 def test_response_headers_encoded(self): 394 def test_response_headers_encoded(self):
415 # prepare environment 395 # prepare environment
diff --git a/glance/tests/unit/test_data_migration_version.py b/glance/tests/unit/test_data_migration_version.py
new file mode 100644
index 0000000..a014838
--- /dev/null
+++ b/glance/tests/unit/test_data_migration_version.py
@@ -0,0 +1,59 @@
1# Copyright 2019 Red Hat, Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16import six
17import testtools
18
19from glance.db.migration import CURRENT_RELEASE
20from glance.version import version_info
21
22
23class TestDataMigrationVersion(testtools.TestCase):
24
25 def test_migration_version(self):
26 """Make sure the data migration version info has been updated."""
27
28 release_number = int(version_info.version_string().split('.', 1)[0])
29
30 # by rule, release names must be composed of the 26 letters of the
31 # ISO Latin alphabet (ord('A')==65, ord('Z')==90)
32 release_letter = six.text_type(CURRENT_RELEASE[:1].upper()).encode(
33 'ascii')
34
35 # Convert release letter into an int in [1:26]. The first
36 # glance release was 'Bexar'.
37 converted_release_letter = (ord(release_letter) -
38 ord(u'B'.encode('ascii')) + 1)
39
40 # Project the release number into [1:26]
41 converted_release_number = release_number % 26
42
43 # Prepare for the worst with a super-informative message
44 msg = ('\n\n'
45 'EMERGENCY!\n'
46 'glance.db.migration.CURRENT_RELEASE is out of sync '
47 'with the glance version.\n'
48 ' CURRENT_RELEASE: %s\n'
49 ' glance version: %s\n'
50 'glance.db.migration.CURRENT_RELEASE needs to be '
51 'updated IMMEDIATELY.\n'
52 'The gate will be wedged until the update is made.\n'
53 'EMERGENCY!\n'
54 '\n') % (CURRENT_RELEASE,
55 version_info.version_string())
56
57 self.assertEqual(converted_release_letter,
58 converted_release_number,
59 msg)
diff --git a/glance/tests/unit/test_domain.py b/glance/tests/unit/test_domain.py
index 5ab5cf7..97037f3 100644
--- a/glance/tests/unit/test_domain.py
+++ b/glance/tests/unit/test_domain.py
@@ -21,8 +21,8 @@ import mock
21from oslo_config import cfg 21from oslo_config import cfg
22import oslo_utils.importutils 22import oslo_utils.importutils
23 23
24import glance.async 24import glance.async_
25from glance.async import taskflow_executor 25from glance.async_ import taskflow_executor
26from glance.common import exception 26from glance.common import exception
27from glance.common import timeutils 27from glance.common import timeutils
28from glance import domain 28from glance import domain
@@ -467,12 +467,12 @@ class TestTask(test_utils.BaseTestCase):
467 self.task.expires_at 467 self.task.expires_at
468 ) 468 )
469 469
470 @mock.patch.object(glance.async.TaskExecutor, 'begin_processing') 470 @mock.patch.object(glance.async_.TaskExecutor, 'begin_processing')
471 def test_run(self, mock_begin_processing): 471 def test_run(self, mock_begin_processing):
472 executor = glance.async.TaskExecutor(context=mock.ANY, 472 executor = glance.async_.TaskExecutor(context=mock.ANY,
473 task_repo=mock.ANY, 473 task_repo=mock.ANY,
474 image_repo=mock.ANY, 474 image_repo=mock.ANY,
475 image_factory=mock.ANY) 475 image_factory=mock.ANY)
476 self.task.run(executor) 476 self.task.run(executor)
477 477
478 mock_begin_processing.assert_called_once_with(self.task.task_id) 478 mock_begin_processing.assert_called_once_with(self.task.task_id)
diff --git a/glance/tests/unit/test_notifier.py b/glance/tests/unit/test_notifier.py
index 04f56f3..346ef5a 100644
--- a/glance/tests/unit/test_notifier.py
+++ b/glance/tests/unit/test_notifier.py
@@ -22,7 +22,7 @@ from oslo_config import cfg
22import oslo_messaging 22import oslo_messaging
23import webob 23import webob
24 24
25import glance.async 25import glance.async_
26from glance.common import exception 26from glance.common import exception
27from glance.common import timeutils 27from glance.common import timeutils
28import glance.context 28import glance.context
@@ -681,7 +681,7 @@ class TestTaskNotifications(utils.BaseTestCase):
681 self.assertEqual(0, len(output_logs)) 681 self.assertEqual(0, len(output_logs))
682 682
683 def test_task_run_notification(self): 683 def test_task_run_notification(self):
684 with mock.patch('glance.async.TaskExecutor') as mock_executor: 684 with mock.patch('glance.async_.TaskExecutor') as mock_executor:
685 executor = mock_executor.return_value 685 executor = mock_executor.return_value
686 executor._run.return_value = mock.Mock() 686 executor._run.return_value = mock.Mock()
687 self.task_proxy.run(executor=mock_executor) 687 self.task_proxy.run(executor=mock_executor)
@@ -694,7 +694,7 @@ class TestTaskNotifications(utils.BaseTestCase):
694 694
695 def test_task_run_notification_disabled(self): 695 def test_task_run_notification_disabled(self):
696 self.config(disabled_notifications=['task.run']) 696 self.config(disabled_notifications=['task.run'])
697 with mock.patch('glance.async.TaskExecutor') as mock_executor: 697 with mock.patch('glance.async_.TaskExecutor') as mock_executor:
698 executor = mock_executor.return_value 698 executor = mock_executor.return_value
699 executor._run.return_value = mock.Mock() 699 executor._run.return_value = mock.Mock()
700 self.task_proxy.run(executor=mock_executor) 700 self.task_proxy.run(executor=mock_executor)
diff --git a/glance/tests/unit/test_quota.py b/glance/tests/unit/test_quota.py
index f024de9..51d0295 100644
--- a/glance/tests/unit/test_quota.py
+++ b/glance/tests/unit/test_quota.py
@@ -12,6 +12,7 @@
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15import copy
15import uuid 16import uuid
16 17
17import mock 18import mock
@@ -596,6 +597,25 @@ class TestQuotaImageTagsProxy(test_utils.BaseTestCase):
596 items.remove(item) 597 items.remove(item)
597 self.assertEqual(0, len(items)) 598 self.assertEqual(0, len(items))
598 599
600 def test_tags_attr_no_loop(self):
601 proxy = glance.quota.QuotaImageTagsProxy(None)
602 self.assertEqual(set([]), proxy.tags)
603
604 def test_tags_deepcopy(self):
605 proxy = glance.quota.QuotaImageTagsProxy(set(['a', 'b']))
606 proxy_copy = copy.deepcopy(proxy)
607 self.assertEqual(set(['a', 'b']), proxy_copy.tags)
608 self.assertIn('a', proxy_copy)
609 # remove is a found via __getattr__
610 proxy_copy.remove('a')
611 self.assertNotIn('a', proxy_copy)
612
613 def test_tags_delete(self):
614 proxy = glance.quota.QuotaImageTagsProxy(set(['a', 'b']))
615 self.assertEqual(set(['a', 'b']), proxy.tags)
616 del proxy.tags
617 self.assertIsNone(proxy.tags)
618
599 619
600class TestImageMemberQuotas(test_utils.BaseTestCase): 620class TestImageMemberQuotas(test_utils.BaseTestCase):
601 621
diff --git a/glance/tests/unit/v2/test_images_resource.py b/glance/tests/unit/v2/test_images_resource.py
index 83e2caf..45b7951 100644
--- a/glance/tests/unit/v2/test_images_resource.py
+++ b/glance/tests/unit/v2/test_images_resource.py
@@ -1720,49 +1720,344 @@ class TestImagesController(base.IsolatedUnitTest):
1720 self.assertEqual(2, len(output.locations)) 1720 self.assertEqual(2, len(output.locations))
1721 self.assertEqual(new_location, output.locations[1]) 1721 self.assertEqual(new_location, output.locations[1])
1722 1722
1723 def test_replace_location_possible_on_queued(self): 1723 @mock.patch.object(glance.quota, '_calc_required_size')
1724 self.skipTest('This test is intermittently failing at the gate. ' 1724 @mock.patch.object(glance.location, '_check_image_location')
1725 'See bug #1649300') 1725 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
1726 @mock.patch.object(store, 'get_size_from_uri_and_backend')
1727 @mock.patch.object(store, 'get_size_from_backend')
1728 def test_replace_locations_on_queued(self,
1729 mock_get_size,
1730 mock_get_size_uri,
1731 mock_set_acls,
1732 mock_check_loc,
1733 mock_calc):
1734 mock_calc.return_value = 1
1735 mock_get_size.return_value = 1
1736 mock_get_size_uri.return_value = 1
1726 self.config(show_multiple_locations=True) 1737 self.config(show_multiple_locations=True)
1738 image_id = str(uuid.uuid4())
1727 self.images = [ 1739 self.images = [
1728 _db_fixture('1', owner=TENANT1, checksum=CHKSUM, 1740 _db_fixture(image_id, owner=TENANT1,
1729 name='1', 1741 name='1',
1730 is_public=True,
1731 disk_format='raw', 1742 disk_format='raw',
1732 container_format='bare', 1743 container_format='bare',
1733 status='queued'), 1744 status='queued',
1745 checksum=None,
1746 os_hash_algo=None,
1747 os_hash_value=None),
1734 ] 1748 ]
1735 self.db.image_create(None, self.images[0]) 1749 self.db.image_create(None, self.images[0])
1736 request = unit_test_utils.get_fake_request() 1750 request = unit_test_utils.get_fake_request()
1737 new_location = {'url': '%s/fake_location_1' % BASE_URI, 'metadata': {}} 1751 new_location1 = {'url': '%s/fake_location_1' % BASE_URI,
1752 'metadata': {},
1753 'validation_data': {'checksum': CHKSUM,
1754 'os_hash_algo': 'sha512',
1755 'os_hash_value': MULTIHASH1}}
1756 new_location2 = {'url': '%s/fake_location_2' % BASE_URI,
1757 'metadata': {},
1758 'validation_data': {'checksum': CHKSUM,
1759 'os_hash_algo': 'sha512',
1760 'os_hash_value': MULTIHASH1}}
1761 changes = [{'op': 'replace', 'path': ['locations'],
1762 'value': [new_location1, new_location2]}]
1763 output = self.controller.update(request, image_id, changes)
1764 self.assertEqual(image_id, output.image_id)
1765 self.assertEqual(2, len(output.locations))
1766 self.assertEqual(new_location1['url'], output.locations[0]['url'])
1767 self.assertEqual(new_location2['url'], output.locations[1]['url'])
1768 self.assertEqual('active', output.status)
1769 self.assertEqual(CHKSUM, output.checksum)
1770 self.assertEqual('sha512', output.os_hash_algo)
1771 self.assertEqual(MULTIHASH1, output.os_hash_value)
1772
1773 @mock.patch.object(glance.quota, '_calc_required_size')
1774 @mock.patch.object(glance.location, '_check_image_location')
1775 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
1776 @mock.patch.object(store, 'get_size_from_uri_and_backend')
1777 @mock.patch.object(store, 'get_size_from_backend')
1778 def test_add_location_new_validation_data_on_active(self,
1779 mock_get_size,
1780 mock_get_size_uri,
1781 mock_set_acls,
1782 mock_check_loc,
1783 mock_calc):
1784 mock_calc.return_value = 1
1785 mock_get_size.return_value = 1
1786 mock_get_size_uri.return_value = 1
1787 self.config(show_multiple_locations=True)
1788 image_id = str(uuid.uuid4())
1789 self.images = [
1790 _db_fixture(image_id, owner=TENANT1,
1791 name='1',
1792 disk_format='raw',
1793 container_format='bare',
1794 status='active',
1795 checksum=None,
1796 os_hash_algo=None,
1797 os_hash_value=None),
1798 ]
1799 self.db.image_create(None, self.images[0])
1800 request = unit_test_utils.get_fake_request()
1801 new_location = {'url': '%s/fake_location_1' % BASE_URI,
1802 'metadata': {},
1803 'validation_data': {'checksum': CHKSUM,
1804 'os_hash_algo': 'sha512',
1805 'os_hash_value': MULTIHASH1}}
1806 changes = [{'op': 'add', 'path': ['locations', '-'],
1807 'value': new_location}]
1808 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1809 "may only be provided when image status "
1810 "is 'queued'",
1811 self.controller.update,
1812 request, image_id, changes)
1813
1814 @mock.patch.object(glance.quota, '_calc_required_size')
1815 @mock.patch.object(glance.location, '_check_image_location')
1816 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
1817 @mock.patch.object(store, 'get_size_from_uri_and_backend')
1818 @mock.patch.object(store, 'get_size_from_backend')
1819 def test_replace_locations_different_validation_data(self,
1820 mock_get_size,
1821 mock_get_size_uri,
1822 mock_set_acls,
1823 mock_check_loc,
1824 mock_calc):
1825 mock_calc.return_value = 1
1826 mock_get_size.return_value = 1
1827 mock_get_size_uri.return_value = 1
1828 self.config(show_multiple_locations=True)
1829 image_id = str(uuid.uuid4())
1830 self.images = [
1831 _db_fixture(image_id, owner=TENANT1,
1832 name='1',
1833 disk_format='raw',
1834 container_format='bare',
1835 status='active',
1836 checksum=CHKSUM,
1837 os_hash_algo='sha512',
1838 os_hash_value=MULTIHASH1),
1839 ]
1840 self.db.image_create(None, self.images[0])
1841 request = unit_test_utils.get_fake_request()
1842 new_location = {'url': '%s/fake_location_1' % BASE_URI,
1843 'metadata': {},
1844 'validation_data': {'checksum': CHKSUM1,
1845 'os_hash_algo': 'sha512',
1846 'os_hash_value': MULTIHASH2}}
1738 changes = [{'op': 'replace', 'path': ['locations'], 1847 changes = [{'op': 'replace', 'path': ['locations'],
1739 'value': [new_location]}] 1848 'value': [new_location]}]
1740 output = self.controller.update(request, '1', changes) 1849 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1741 self.assertEqual('1', output.image_id) 1850 "already set with a different value",
1851 self.controller.update,
1852 request, image_id, changes)
1853
1854 @mock.patch.object(glance.quota, '_calc_required_size')
1855 @mock.patch.object(glance.location, '_check_image_location')
1856 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
1857 @mock.patch.object(store, 'get_size_from_uri_and_backend')
1858 @mock.patch.object(store, 'get_size_from_backend')
1859 def test_add_location_on_queued(self,
1860 mock_get_size,
1861 mock_get_size_uri,
1862 mock_set_acls,
1863 mock_check_loc,
1864 mock_calc):
1865 mock_calc.return_value = 1
1866 mock_get_size.return_value = 1
1867 mock_get_size_uri.return_value = 1
1868 self.config(show_multiple_locations=True)
1869 image_id = str(uuid.uuid4())
1870 self.images = [
1871 _db_fixture(image_id, owner=TENANT1, checksum=CHKSUM,
1872 name='1',
1873 disk_format='raw',
1874 container_format='bare',
1875 status='queued'),
1876 ]
1877 self.db.image_create(None, self.images[0])
1878 request = unit_test_utils.get_fake_request()
1879 new_location = {'url': '%s/fake_location_1' % BASE_URI,
1880 'metadata': {}}
1881 changes = [{'op': 'add', 'path': ['locations', '-'],
1882 'value': new_location}]
1883 output = self.controller.update(request, image_id, changes)
1884 self.assertEqual(image_id, output.image_id)
1742 self.assertEqual(1, len(output.locations)) 1885 self.assertEqual(1, len(output.locations))
1743 self.assertEqual(new_location, output.locations[0]) 1886 self.assertEqual(new_location, output.locations[0])
1887 self.assertEqual('active', output.status)
1744 1888
1745 def test_add_location_possible_on_queued(self): 1889 @mock.patch.object(glance.quota, '_calc_required_size')
1746 self.skipTest('This test is intermittently failing at the gate. ' 1890 @mock.patch.object(glance.location, '_check_image_location')
1747 'See bug #1649300') 1891 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
1892 @mock.patch.object(store, 'get_size_from_uri_and_backend')
1893 @mock.patch.object(store, 'get_size_from_backend')
1894 def test_add_location_invalid_validation_data(self,
1895 mock_get_size,
1896 mock_get_size_uri,
1897 mock_set_acls,
1898 mock_check_loc,
1899 mock_calc):
1900 mock_calc.return_value = 1
1901 mock_get_size.return_value = 1
1902 mock_get_size_uri.return_value = 1
1748 self.config(show_multiple_locations=True) 1903 self.config(show_multiple_locations=True)
1904 image_id = str(uuid.uuid4())
1749 self.images = [ 1905 self.images = [
1750 _db_fixture('1', owner=TENANT1, checksum=CHKSUM, 1906 _db_fixture(image_id, owner=TENANT1,
1907 checksum=None,
1908 os_hash_algo=None,
1909 os_hash_value=None,
1751 name='1', 1910 name='1',
1752 is_public=True,
1753 disk_format='raw', 1911 disk_format='raw',
1754 container_format='bare', 1912 container_format='bare',
1755 status='queued'), 1913 status='queued'),
1756 ] 1914 ]
1757 self.db.image_create(None, self.images[0]) 1915 self.db.image_create(None, self.images[0])
1758 request = unit_test_utils.get_fake_request() 1916 request = unit_test_utils.get_fake_request()
1759 new_location = {'url': '%s/fake_location_1' % BASE_URI, 'metadata': {}} 1917
1918 location = {
1919 'url': '%s/fake_location_1' % BASE_URI,
1920 'metadata': {},
1921 'validation_data': {}
1922 }
1923 changes = [{'op': 'add', 'path': ['locations', '-'],
1924 'value': location}]
1925
1926 changes[0]['value']['validation_data'] = {
1927 'checksum': 'something the same length as md5',
1928 'os_hash_algo': 'sha512',
1929 'os_hash_value': MULTIHASH1,
1930 }
1931 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1932 'checksum .* is not a valid hexadecimal value',
1933 self.controller.update,
1934 request, image_id, changes)
1935
1936 changes[0]['value']['validation_data'] = {
1937 'checksum': '0123456789abcdef',
1938 'os_hash_algo': 'sha512',
1939 'os_hash_value': MULTIHASH1,
1940 }
1941 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1942 'checksum .* is not the correct size',
1943 self.controller.update,
1944 request, image_id, changes)
1945
1946 changes[0]['value']['validation_data'] = {
1947 'checksum': CHKSUM,
1948 'os_hash_algo': 'sha256',
1949 'os_hash_value': MULTIHASH1,
1950 }
1951 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1952 'os_hash_algo must be sha512',
1953 self.controller.update,
1954 request, image_id, changes)
1955
1956 changes[0]['value']['validation_data'] = {
1957 'checksum': CHKSUM,
1958 'os_hash_algo': 'sha512',
1959 'os_hash_value': 'not a hex value',
1960 }
1961 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1962 'os_hash_value .* is not a valid hexadecimal '
1963 'value',
1964 self.controller.update,
1965 request, image_id, changes)
1966
1967 changes[0]['value']['validation_data'] = {
1968 'checksum': CHKSUM,
1969 'os_hash_algo': 'sha512',
1970 'os_hash_value': '0123456789abcdef',
1971 }
1972 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1973 'os_hash_value .* is not the correct size '
1974 'for sha512',
1975 self.controller.update,
1976 request, image_id, changes)
1977
1978 @mock.patch.object(glance.quota, '_calc_required_size')
1979 @mock.patch.object(glance.location, '_check_image_location')
1980 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
1981 @mock.patch.object(store, 'get_size_from_uri_and_backend')
1982 @mock.patch.object(store, 'get_size_from_backend')
1983 def test_add_location_same_validation_data(self,
1984 mock_get_size,
1985 mock_get_size_uri,
1986 mock_set_acls,
1987 mock_check_loc,
1988 mock_calc):
1989 mock_calc.return_value = 1
1990 mock_get_size.return_value = 1
1991 mock_get_size_uri.return_value = 1
1992 self.config(show_multiple_locations=True)
1993 image_id = str(uuid.uuid4())
1994 os_hash_value = '6513f21e44aa3da349f248188a44bc304a3653a04122d8fb45' \
1995 '35423c8e1d14cd6a153f735bb0982e2161b5b5186106570c17' \
1996 'a9e58b64dd39390617cd5a350f78'
1997 self.images = [
1998 _db_fixture(image_id, owner=TENANT1,
1999 name='1',
2000 disk_format='raw',
2001 container_format='bare',
2002 status='active',
2003 checksum='checksum1',
2004 os_hash_algo='sha512',
2005 os_hash_value=os_hash_value),
2006 ]
2007 self.db.image_create(None, self.images[0])
2008 request = unit_test_utils.get_fake_request()
2009 new_location = {'url': '%s/fake_location_1' % BASE_URI,
2010 'metadata': {},
2011 'validation_data': {'checksum': 'checksum1',
2012 'os_hash_algo': 'sha512',
2013 'os_hash_value': os_hash_value}}
1760 changes = [{'op': 'add', 'path': ['locations', '-'], 2014 changes = [{'op': 'add', 'path': ['locations', '-'],
1761 'value': new_location}] 2015 'value': new_location}]
1762 output = self.controller.update(request, '1', changes) 2016 output = self.controller.update(request, image_id, changes)
1763 self.assertEqual('1', output.image_id) 2017 self.assertEqual(image_id, output.image_id)
1764 self.assertEqual(1, len(output.locations)) 2018 self.assertEqual(1, len(output.locations))
1765 self.assertEqual(new_location, output.locations[0]) 2019 self.assertEqual(new_location, output.locations[0])
2020 self.assertEqual('active', output.status)
2021
2022 @mock.patch.object(glance.quota, '_calc_required_size')
2023 @mock.patch.object(glance.location, '_check_image_location')
2024 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
2025 @mock.patch.object(store, 'get_size_from_uri_and_backend')
2026 @mock.patch.object(store, 'get_size_from_backend')
2027 def test_add_location_different_validation_data(self,
2028 mock_get_size,
2029 mock_get_size_uri,
2030 mock_set_acls,
2031 mock_check_loc,
2032 mock_calc):
2033 mock_calc.return_value = 1
2034 mock_get_size.return_value = 1
2035 mock_get_size_uri.return_value = 1
2036 self.config(show_multiple_locations=True)
2037 image_id = str(uuid.uuid4())
2038 self.images = [
2039 _db_fixture(image_id, owner=TENANT1,
2040 name='1',
2041 disk_format='raw',
2042 container_format='bare',
2043 status='active',
2044 checksum=CHKSUM,
2045 os_hash_algo='sha512',
2046 os_hash_value=MULTIHASH1),
2047 ]
2048 self.db.image_create(None, self.images[0])
2049 request = unit_test_utils.get_fake_request()
2050 new_location = {'url': '%s/fake_location_1' % BASE_URI,
2051 'metadata': {},
2052 'validation_data': {'checksum': CHKSUM1,
2053 'os_hash_algo': 'sha512',
2054 'os_hash_value': MULTIHASH2}}
2055 changes = [{'op': 'add', 'path': ['locations', '-'],
2056 'value': new_location}]
2057 self.assertRaisesRegexp(webob.exc.HTTPConflict,
2058 "already set with a different value",
2059 self.controller.update,
2060 request, image_id, changes)
1766 2061
1767 def _test_update_locations_status(self, image_status, update): 2062 def _test_update_locations_status(self, image_status, update):
1768 self.config(show_multiple_locations=True) 2063 self.config(show_multiple_locations=True)
@@ -1799,6 +2094,12 @@ class TestImagesController(base.IsolatedUnitTest):
1799 def test_location_add_not_permitted_status_killed(self): 2094 def test_location_add_not_permitted_status_killed(self):
1800 self._test_update_locations_status('killed', 'add') 2095 self._test_update_locations_status('killed', 'add')
1801 2096
2097 def test_location_add_not_permitted_status_importing(self):
2098 self._test_update_locations_status('importing', 'add')
2099
2100 def test_location_add_not_permitted_status_uploading(self):
2101 self._test_update_locations_status('uploading', 'add')
2102
1802 def test_location_remove_not_permitted_status_saving(self): 2103 def test_location_remove_not_permitted_status_saving(self):
1803 self._test_update_locations_status('saving', 'remove') 2104 self._test_update_locations_status('saving', 'remove')
1804 2105
@@ -1817,6 +2118,12 @@ class TestImagesController(base.IsolatedUnitTest):
1817 def test_location_remove_not_permitted_status_queued(self): 2118 def test_location_remove_not_permitted_status_queued(self):
1818 self._test_update_locations_status('queued', 'remove') 2119 self._test_update_locations_status('queued', 'remove')
1819 2120
2121 def test_location_remove_not_permitted_status_importing(self):
2122 self._test_update_locations_status('importing', 'remove')
2123
2124 def test_location_remove_not_permitted_status_uploading(self):
2125 self._test_update_locations_status('uploading', 'remove')
2126
1820 def test_location_replace_not_permitted_status_saving(self): 2127 def test_location_replace_not_permitted_status_saving(self):
1821 self._test_update_locations_status('saving', 'replace') 2128 self._test_update_locations_status('saving', 'replace')
1822 2129
@@ -1832,6 +2139,12 @@ class TestImagesController(base.IsolatedUnitTest):
1832 def test_location_replace_not_permitted_status_killed(self): 2139 def test_location_replace_not_permitted_status_killed(self):
1833 self._test_update_locations_status('killed', 'replace') 2140 self._test_update_locations_status('killed', 'replace')
1834 2141
2142 def test_location_replace_not_permitted_status_importing(self):
2143 self._test_update_locations_status('importing', 'replace')
2144
2145 def test_location_replace_not_permitted_status_uploading(self):
2146 self._test_update_locations_status('uploading', 'replace')
2147
1835 def test_update_add_locations_insertion(self): 2148 def test_update_add_locations_insertion(self):
1836 self.config(show_multiple_locations=True) 2149 self.config(show_multiple_locations=True)
1837 new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}} 2150 new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
@@ -2656,6 +2969,44 @@ class TestImagesDeserializer(test_utils.BaseTestCase):
2656 self.assertRaises(webob.exc.HTTPBadRequest, 2969 self.assertRaises(webob.exc.HTTPBadRequest,
2657 self.deserializer.update, request) 2970 self.deserializer.update, request)
2658 2971
2972 def test_update_invalid_validation_data(self):
2973 request = self._get_fake_patch_request()
2974 changes = [{
2975 'op': 'add',
2976 'path': '/locations/0',
2977 'value': {
2978 'url': 'http://localhost/fake',
2979 'metadata': {},
2980 }
2981 }]
2982
2983 changes[0]['value']['validation_data'] = {
2984 'os_hash_algo': 'sha512',
2985 'os_hash_value': MULTIHASH1,
2986 'checksum': CHKSUM,
2987 }
2988 request.body = jsonutils.dump_as_bytes(changes)
2989 self.deserializer.update(request)
2990
2991 changes[0]['value']['validation_data'] = {
2992 'os_hash_algo': 'sha512',
2993 'os_hash_value': MULTIHASH1,
2994 'checksum': CHKSUM,
2995 'bogus_key': 'bogus_value',
2996 }
2997 request.body = jsonutils.dump_as_bytes(changes)
2998 self.assertRaisesRegexp(webob.exc.HTTPBadRequest,
2999 'Additional properties are not allowed',
3000 self.deserializer.update, request)
3001
3002 changes[0]['value']['validation_data'] = {
3003 'checksum': CHKSUM,
3004 }
3005 request.body = jsonutils.dump_as_bytes(changes)
3006 self.assertRaisesRegexp(webob.exc.HTTPBadRequest,
3007 'os_hash.* is a required property',
3008 self.deserializer.update, request)
3009
2659 def test_update(self): 3010 def test_update(self):
2660 request = self._get_fake_patch_request() 3011 request = self._get_fake_patch_request()
2661 body = [ 3012 body = [
diff --git a/glance/tests/utils.py b/glance/tests/utils.py
index 0b0d143..140a772 100644
--- a/glance/tests/utils.py
+++ b/glance/tests/utils.py
@@ -31,7 +31,6 @@ from oslo_config import fixture as cfg_fixture
31from oslo_log.fixture import logging_error as log_fixture 31from oslo_log.fixture import logging_error as log_fixture
32from oslo_log import log 32from oslo_log import log
33from oslo_serialization import jsonutils 33from oslo_serialization import jsonutils
34from oslotest import moxstubout
35import six 34import six
36from six.moves import BaseHTTPServer 35from six.moves import BaseHTTPServer
37from six.moves import http_client as http 36from six.moves import http_client as http
@@ -78,8 +77,6 @@ class BaseTestCase(testtools.TestCase):
78 # the following policy tests 77 # the following policy tests
79 config.parse_args(args=[]) 78 config.parse_args(args=[])
80 self.addCleanup(CONF.reset) 79 self.addCleanup(CONF.reset)
81 mox_fixture = self.useFixture(moxstubout.MoxStubout())
82 self.stubs = mox_fixture.stubs
83 self.mock_object(exception, '_FATAL_EXCEPTION_FORMAT_ERRORS', True) 80 self.mock_object(exception, '_FATAL_EXCEPTION_FORMAT_ERRORS', True)
84 self.test_dir = self.useFixture(fixtures.TempDir()).path 81 self.test_dir = self.useFixture(fixtures.TempDir()).path
85 self.conf_dir = os.path.join(self.test_dir, 'etc') 82 self.conf_dir = os.path.join(self.test_dir, 'etc')
@@ -713,3 +710,28 @@ def is_sqlite_version_prior_to(major, minor):
713 import sqlite3 710 import sqlite3
714 tup = sqlite3.sqlite_version_info 711 tup = sqlite3.sqlite_version_info
715 return tup[0] < major or (tup[0] == major and tup[1] < minor) 712 return tup[0] < major or (tup[0] == major and tup[1] < minor)
713
714
715def start_standalone_http_server():
716 def _get_http_handler_class():
717 class StaticHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
718 def do_GET(self):
719 data = b"Hello World!!!"
720 self.send_response(http.OK)
721 self.send_header('Content-Length', str(len(data)))
722 self.end_headers()
723 self.wfile.write(data)
724 return
725
726 return StaticHTTPRequestHandler
727
728 server_address = ('127.0.0.1', 0)
729 handler_class = _get_http_handler_class()
730 httpd = BaseHTTPServer.HTTPServer(server_address, handler_class)
731 port = httpd.socket.getsockname()[1]
732
733 pid = os.fork()
734 if pid == 0:
735 httpd.serve_forever()
736 else:
737 return pid, port
diff --git a/lower-constraints.txt b/lower-constraints.txt
index beac1f1..6beaebe 100644
--- a/lower-constraints.txt
+++ b/lower-constraints.txt
@@ -52,7 +52,6 @@ Mako==1.0.7
52MarkupSafe==1.0 52MarkupSafe==1.0
53mccabe==0.2.1 53mccabe==0.2.1
54mock==2.0.0 54mock==2.0.0
55monotonic==0.6
56mox3==0.25.0 55mox3==0.25.0
57msgpack==0.5.6 56msgpack==0.5.6
58netaddr==0.7.19 57netaddr==0.7.19
@@ -74,6 +73,7 @@ oslo.middleware==3.31.0
74oslo.policy==1.30.0 73oslo.policy==1.30.0
75oslo.serialization==2.25.0 74oslo.serialization==2.25.0
76oslo.service==1.30.0 75oslo.service==1.30.0
76oslo.upgradecheck==0.1.0
77oslo.utils==3.33.0 77oslo.utils==3.33.0
78oslotest==3.2.0 78oslotest==3.2.0
79osprofiler==1.4.0 79osprofiler==1.4.0
@@ -138,7 +138,7 @@ unittest2==1.1.0
138urllib3==1.22 138urllib3==1.22
139vine==1.1.4 139vine==1.1.4
140voluptuous==0.11.1 140voluptuous==0.11.1
141WebOb==1.7.1 141WebOb==1.8.1
142whereto===0.3.0 142whereto===0.3.0
143wrapt==1.10.11 143wrapt==1.10.11
144WSME==0.8.0 144WSME==0.8.0
diff --git a/releasenotes/notes/bp-upgrade-checks-b3272c3ddb4e8cf7.yaml b/releasenotes/notes/bp-upgrade-checks-b3272c3ddb4e8cf7.yaml
new file mode 100644
index 0000000..241eec4
--- /dev/null
+++ b/releasenotes/notes/bp-upgrade-checks-b3272c3ddb4e8cf7.yaml
@@ -0,0 +1,9 @@
1---
2features:
3 - |
4 [`Community Goal <https://governance.openstack.org/tc/goals/stein/upgrade-checkers.html>`_]
5 Support has been added for developers to write pre-upgrade checks.
6 Operators can run these checks using ``glance-status upgrade check``.
7 This allows operators to be more confident when upgrading their deployments
8 by having a tool that automates programmable checks against the deployment
9 configuration or dataset.
diff --git a/releasenotes/notes/deprecate-show-multiple-location-continued-646f91b21cd771f7.yaml b/releasenotes/notes/deprecate-show-multiple-location-continued-646f91b21cd771f7.yaml
new file mode 100644
index 0000000..147dbaf
--- /dev/null
+++ b/releasenotes/notes/deprecate-show-multiple-location-continued-646f91b21cd771f7.yaml
@@ -0,0 +1,23 @@
1---
2upgrade:
3 - |
4 The ``show_multiple_locations`` configuration option remains deprecated in
5 this release, but it has not been removed. (It had been scheduled for
6 removal in the Pike release.) Please keep a watch on the Glance release
7 notes and the glance-specs repository to stay informed about developments
8 on this issue.
9
10 The plan is to eliminate the option and use only policies to control image
11 locations access. This, however, requires some major refactoring. See the
12 `draft Policy Refactor spec <https://review.openstack.org/#/c/528021/>`_
13 for more information.
14
15 There is no projected timeline for this change, as no one has been able to
16 commit time to it. The Glance team would be happy to discuss this more
17 with anyone interested in working on it.
18
19 The workaround is to continue to use the ``show_multiple_locations`` option
20 in a dedicated "internal" Glance node that is not accessible to end users.
21 We continue to recommend that image locations not be exposed to end users.
22 See `OSSN-0065 <https://wiki.openstack.org/wiki/OSSN/OSSN-0065>`_ for more
23 information.
diff --git a/releasenotes/notes/use-webob-1.8.1-5c3cd1b1382f063e.yaml b/releasenotes/notes/use-webob-1.8.1-5c3cd1b1382f063e.yaml
new file mode 100644
index 0000000..21d8d3e
--- /dev/null
+++ b/releasenotes/notes/use-webob-1.8.1-5c3cd1b1382f063e.yaml
@@ -0,0 +1,12 @@
1---
2other:
3 - |
4 Negotiation of the 'Accept-Language' header now follows the "Lookup"
5 matching scheme described in `RFC 4647, section 3.4
6 <https://tools.ietf.org/html/rfc4647.html#section-3.4>`_. The
7 "Lookup" scheme is one of the algorithms suggested in `RFC 7231,
8 section 5.3.5
9 <https://tools.ietf.org/html/rfc7231.html#section-5.3.5>`_. (This is
10 due to a change in an underlying library, which previously used a
11 matching scheme that did not conform to `RFC 7231
12 <https://tools.ietf.org/html/rfc7231.html>`_.)
diff --git a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
index 076ea6b..c683246 100644
--- a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
+++ b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
@@ -4,11 +4,11 @@ msgid ""
4msgstr "" 4msgstr ""
5"Project-Id-Version: Glance Release Notes\n" 5"Project-Id-Version: Glance Release Notes\n"
6"Report-Msgid-Bugs-To: \n" 6"Report-Msgid-Bugs-To: \n"
7"POT-Creation-Date: 2018-08-16 22:24+0000\n" 7"POT-Creation-Date: 2018-11-20 06:33+0000\n"
8"MIME-Version: 1.0\n" 8"MIME-Version: 1.0\n"
9"Content-Type: text/plain; charset=UTF-8\n" 9"Content-Type: text/plain; charset=UTF-8\n"
10"Content-Transfer-Encoding: 8bit\n" 10"Content-Transfer-Encoding: 8bit\n"
11"PO-Revision-Date: 2018-08-17 09:31+0000\n" 11"PO-Revision-Date: 2018-12-04 05:26+0000\n"
12"Last-Translator: Andi Chandler <andi@gowling.com>\n" 12"Last-Translator: Andi Chandler <andi@gowling.com>\n"
13"Language-Team: English (United Kingdom)\n" 13"Language-Team: English (United Kingdom)\n"
14"Language: en_GB\n" 14"Language: en_GB\n"
@@ -111,20 +111,14 @@ msgstr "16.0.0"
111msgid "16.0.1" 111msgid "16.0.1"
112msgstr "16.0.1" 112msgstr "16.0.1"
113 113
114msgid "17.0.0.0b1" 114msgid "17.0.0"
115msgstr "17.0.0.0b1" 115msgstr "17.0.0"
116
117msgid "17.0.0.0b2"
118msgstr "17.0.0.0b2"
119
120msgid "17.0.0.0b3"
121msgstr "17.0.0.0b3"
122 116
123msgid "17.0.0.0rc1" 117msgid "17.0.0.0rc1"
124msgstr "17.0.0.0rc1" 118msgstr "17.0.0.0rc1"
125 119
126msgid "17.0.0.0rc1-6" 120msgid "17.0.0.0rc1-40"
127msgstr "17.0.0.0rc1-6" 121msgstr "17.0.0.0rc1-40"
128 122
129msgid "" 123msgid ""
130"A new interoperable image import method, ``web-download`` is introduced." 124"A new interoperable image import method, ``web-download`` is introduced."
@@ -1283,6 +1277,23 @@ msgstr ""
1283msgid "Mitaka Series Release Notes" 1277msgid "Mitaka Series Release Notes"
1284msgstr "Mitaka Series Release Notes" 1278msgstr "Mitaka Series Release Notes"
1285 1279
1280msgid ""
1281"Negotiation of the 'Accept-Language' header now follows the \"Lookup\" "
1282"matching scheme described in `RFC 4647, section 3.4 <https://tools.ietf.org/"
1283"html/rfc4647.html#section-3.4>`_. The \"Lookup\" scheme is one of the "
1284"algorithms suggested in `RFC 7231, section 5.3.5 <https://tools.ietf.org/"
1285"html/rfc7231.html#section-5.3.5>`_. (This is due to a change in an "
1286"underlying library, which previously used a matching scheme that did not "
1287"conform to `RFC 7231 <https://tools.ietf.org/html/rfc7231.html>`_.)"
1288msgstr ""
1289"Negotiation of the 'Accept-Language' header now follows the \"Lookup\" "
1290"matching scheme described in `RFC 4647, section 3.4 <https://tools.ietf.org/"
1291"html/rfc4647.html#section-3.4>`_. The \"Lookup\" scheme is one of the "
1292"algorithms suggested in `RFC 7231, section 5.3.5 <https://tools.ietf.org/"
1293"html/rfc7231.html#section-5.3.5>`_. (This is due to a change in an "
1294"underlying library, which previously used a matching scheme that did not "
1295"conform to `RFC 7231 <https://tools.ietf.org/html/rfc7231.html>`_.)"
1296
1286msgid "New Features" 1297msgid "New Features"
1287msgstr "New Features" 1298msgstr "New Features"
1288 1299
diff --git a/requirements.txt b/requirements.txt
index 1b50271..407a00b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,7 +10,7 @@ SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT
10eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT 10eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT
11PasteDeploy>=1.5.0 # MIT 11PasteDeploy>=1.5.0 # MIT
12Routes>=2.3.1 # MIT 12Routes>=2.3.1 # MIT
13WebOb>=1.7.1 # MIT 13WebOb>=1.8.1 # MIT
14sqlalchemy-migrate>=0.11.0 # Apache-2.0 14sqlalchemy-migrate>=0.11.0 # Apache-2.0
15sqlparse>=0.2.2 # BSD 15sqlparse>=0.2.2 # BSD
16alembic>=0.8.10 # MIT 16alembic>=0.8.10 # MIT
@@ -18,6 +18,7 @@ httplib2>=0.9.1 # MIT
18oslo.config>=5.2.0 # Apache-2.0 18oslo.config>=5.2.0 # Apache-2.0
19oslo.concurrency>=3.26.0 # Apache-2.0 19oslo.concurrency>=3.26.0 # Apache-2.0
20oslo.context>=2.19.2 # Apache-2.0 20oslo.context>=2.19.2 # Apache-2.0
21oslo.upgradecheck>=0.1.0 # Apache-2.0
21oslo.utils>=3.33.0 # Apache-2.0 22oslo.utils>=3.33.0 # Apache-2.0
22stevedore>=1.20.0 # Apache-2.0 23stevedore>=1.20.0 # Apache-2.0
23futurist>=1.2.0 # Apache-2.0 24futurist>=1.2.0 # Apache-2.0
@@ -38,7 +39,7 @@ six>=1.10.0 # MIT
38oslo.db>=4.27.0 # Apache-2.0 39oslo.db>=4.27.0 # Apache-2.0
39oslo.i18n>=3.15.3 # Apache-2.0 40oslo.i18n>=3.15.3 # Apache-2.0
40oslo.log>=3.36.0 # Apache-2.0 41oslo.log>=3.36.0 # Apache-2.0
41oslo.messaging>=5.29.0 # Apache-2.0 42oslo.messaging>=5.29.0,!=9.0.0 # Apache-2.0
42oslo.middleware>=3.31.0 # Apache-2.0 43oslo.middleware>=3.31.0 # Apache-2.0
43oslo.policy>=1.30.0 # Apache-2.0 44oslo.policy>=1.30.0 # Apache-2.0
44 45
@@ -55,4 +56,3 @@ cursive>=0.2.1 # Apache-2.0
55 56
56# timeutils 57# timeutils
57iso8601>=0.1.11 # MIT 58iso8601>=0.1.11 # MIT
58monotonic>=0.6 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 9e373f5..03beee8 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -4,7 +4,7 @@ summary = OpenStack Image Service
4description-file = 4description-file =
5 README.rst 5 README.rst
6author = OpenStack 6author = OpenStack
7author-email = openstack-dev@lists.openstack.org 7author-email = openstack-discuss@lists.openstack.org
8home-page = https://docs.openstack.org/glance/latest/ 8home-page = https://docs.openstack.org/glance/latest/
9classifier = 9classifier =
10 Environment :: OpenStack 10 Environment :: OpenStack
@@ -27,7 +27,6 @@ data_files =
27 etc/glance-api-paste.ini 27 etc/glance-api-paste.ini
28 etc/glance-registry-paste.ini 28 etc/glance-registry-paste.ini
29 etc/policy.json 29 etc/policy.json
30 etc/rootwrap.conf
31 etc/glance/metadefs = etc/metadefs/* 30 etc/glance/metadefs = etc/metadefs/*
32packages = 31packages =
33 glance 32 glance
@@ -44,6 +43,7 @@ console_scripts =
44 glance-registry = glance.cmd.registry:main 43 glance-registry = glance.cmd.registry:main
45 glance-replicator = glance.cmd.replicator:main 44 glance-replicator = glance.cmd.replicator:main
46 glance-scrubber = glance.cmd.scrubber:main 45 glance-scrubber = glance.cmd.scrubber:main
46 glance-status = glance.cmd.status:main
47wsgi_scripts = 47wsgi_scripts =
48 glance-wsgi-api = glance.common.wsgi_app:init_app 48 glance-wsgi-api = glance.common.wsgi_app:init_app
49glance.common.image_location_strategy.modules = 49glance.common.image_location_strategy.modules =
@@ -64,21 +64,21 @@ glance.database.metadata_backend =
64 sqlalchemy = glance.db.sqlalchemy.metadata 64 sqlalchemy = glance.db.sqlalchemy.metadata
65 65
66glance.flows = 66glance.flows =
67 api_image_import = glance.async.flows.api_image_import:get_flow 67 api_image_import = glance.async_.flows.api_image_import:get_flow
68 import = glance.async.flows.base_import:get_flow 68 import = glance.async_.flows.base_import:get_flow
69 69
70glance.flows.import = 70glance.flows.import =
71 convert = glance.async.flows.convert:get_flow 71 convert = glance.async_.flows.convert:get_flow
72 introspect = glance.async.flows.introspect:get_flow 72 introspect = glance.async_.flows.introspect:get_flow
73 ovf_process = glance.async.flows.ovf_process:get_flow 73 ovf_process = glance.async_.flows.ovf_process:get_flow
74 74
75glance.image_import.plugins = 75glance.image_import.plugins =
76 no_op = glance.async.flows.plugins.no_op:get_flow 76 no_op = glance.async_.flows.plugins.no_op:get_flow
77 inject_image_metadata=glance.async.flows.plugins.inject_image_metadata:get_flow 77 inject_image_metadata=glance.async_.flows.plugins.inject_image_metadata:get_flow
78 image_conversion=glance.async.flows.plugins.image_conversion:get_flow 78 image_conversion=glance.async_.flows.plugins.image_conversion:get_flow
79 79
80glance.image_import.internal_plugins = 80glance.image_import.internal_plugins =
81 web_download = glance.async.flows._internal_plugins.web_download:get_flow 81 web_download = glance.async_.flows._internal_plugins.web_download:get_flow
82 82
83 83
84[egg_info] 84[egg_info]
diff --git a/test-requirements.txt b/test-requirements.txt
index 01a708b..4234414 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -28,6 +28,5 @@ doc8>=0.6.0 # Apache-2.0
28PyMySQL>=0.7.6 # MIT License 28PyMySQL>=0.7.6 # MIT License
29psycopg2>=2.6.2 # LGPL/ZPL 29psycopg2>=2.6.2 # LGPL/ZPL
30pysendfile>=2.0.0 # MIT 30pysendfile>=2.0.0 # MIT
31qpid-python>=0.26;python_version=='2.7' # Apache-2.0
32xattr>=0.9.2 # MIT 31xattr>=0.9.2 # MIT
33python-swiftclient>=3.2.0 # Apache-2.0 32python-swiftclient>=3.2.0 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index 0f88bc3..8692ec6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -34,6 +34,10 @@ commands = ostestr --slowest {posargs}
34basepython = python3.5 34basepython = python3.5
35commands = ostestr --slowest {posargs} 35commands = ostestr --slowest {posargs}
36 36
37[testenv:py37]
38basepython = python3.7
39commands = ostestr --slowest {posargs}
40
37[testenv:functional] 41[testenv:functional]
38setenv = 42setenv =
39 TEST_PATH = ./glance/tests/functional 43 TEST_PATH = ./glance/tests/functional
@@ -51,6 +55,16 @@ whitelist_externals =
51commands = 55commands =
52 stestr run --blacklist-file ./broken-functional-py35-ssl-tests.txt {posargs} 56 stestr run --blacklist-file ./broken-functional-py35-ssl-tests.txt {posargs}
53 57
58[testenv:functional-py37]
59basepython = python3.7
60setenv =
61 TEST_PATH = ./glance/tests/functional
62ignore_errors = True
63whitelist_externals =
64 bash
65commands =
66 stestr run --blacklist-file ./broken-functional-py35-ssl-tests.txt {posargs}
67
54[testenv:broken-py35-ssl-tests] 68[testenv:broken-py35-ssl-tests]
55# NOTE(rosmaita): these tests were being skipped due to bug #1482633, but we 69# NOTE(rosmaita): these tests were being skipped due to bug #1482633, but we
56# want it to be obvious that Glance is affected by the eventlet ssl-handshake 70# want it to be obvious that Glance is affected by the eventlet ssl-handshake