Skip to content

Commit 9eea28b

Browse files
rajatherestephenfin
authored andcommitted
Fix: create image from volume command
Currently the command ``openstack image create --volume`` calls cinderclient to upload the volume to image service (glance) but OSC passes ``visibility`` and ``protected`` fields which are only available in microversion 3.1 or greater. This generates an error if the user is using volume microversion < 3.1 and wants to create an image from volume. This patch fixes that by only passing ``visibility`` and ``protected`` fields when the volume microversion is 3.1 or greater and fail otherwise i.e. the following 3 cases: 1) visibility/protected argument + mv >= 3.1 = pass 2) visibility/protected argument + mv < 3.1 = fail 3) not visibility/protected argument + any mv = pass Story: 2010060 Task: 45511 Change-Id: I568a0ea0af8f7f82b16d49a6a1bb0391b99c50dc
1 parent 20e7b01 commit 9eea28b

3 files changed

Lines changed: 128 additions & 3 deletions

File tree

openstackclient/image/v2/image.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import os
2222
import sys
2323

24+
from cinderclient import api_versions
2425
from openstack.image import image_signer
2526
from osc_lib.api import utils as api_utils
2627
from osc_lib.cli import format_columns
@@ -483,14 +484,27 @@ def take_action(self, parsed_args):
483484
volume_client.volumes,
484485
parsed_args.volume,
485486
)
487+
mv_kwargs = {}
488+
if volume_client.api_version >= api_versions.APIVersion('3.1'):
489+
mv_kwargs.update(
490+
visibility=kwargs.get('visibility', 'private'),
491+
protected=bool(parsed_args.protected)
492+
)
493+
else:
494+
if kwargs.get('visibility') or parsed_args.protected:
495+
msg = _(
496+
'--os-volume-api-version 3.1 or greater is required '
497+
'to support the --public, --private, --community, '
498+
'--shared or --protected option.'
499+
)
500+
raise exceptions.CommandError(msg)
486501
response, body = volume_client.volumes.upload_to_image(
487502
source_volume.id,
488503
parsed_args.force,
489504
parsed_args.name,
490505
parsed_args.container_format,
491506
parsed_args.disk_format,
492-
visibility=kwargs.get('visibility', 'private'),
493-
protected=True if parsed_args.protected else False
507+
**mv_kwargs
494508
)
495509
info = body['os-volume_upload_image']
496510
try:

openstackclient/tests/unit/image/v2/test_image.py

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,18 @@
1818
import tempfile
1919
from unittest import mock
2020

21+
from cinderclient import api_versions
2122
from openstack import exceptions as sdk_exceptions
2223
from osc_lib.cli import format_columns
2324
from osc_lib import exceptions
2425

2526
from openstackclient.image.v2 import image
2627
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
2728
from openstackclient.tests.unit.image.v2 import fakes as image_fakes
29+
from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes
2830

2931

30-
class TestImage(image_fakes.TestImagev2):
32+
class TestImage(image_fakes.TestImagev2, volume_fakes.TestVolume):
3133

3234
def setUp(self):
3335
super(TestImage, self).setUp()
@@ -40,6 +42,13 @@ def setUp(self):
4042
self.project_mock.reset_mock()
4143
self.domain_mock = self.app.client_manager.identity.domains
4244
self.domain_mock.reset_mock()
45+
self.volumes_mock = self.app.client_manager.volume.volumes
46+
fake_body = {
47+
'os-volume_upload_image':
48+
{'volume_type': {'name': 'fake_type'}}}
49+
self.volumes_mock.upload_to_image.return_value = (
50+
200, fake_body)
51+
self.volumes_mock.reset_mock()
4352

4453
def setup_images_mock(self, count):
4554
images = image_fakes.create_images(count=count)
@@ -287,6 +296,101 @@ def test_image_create_import(self, raw_input):
287296
use_import=True
288297
)
289298

299+
@mock.patch('osc_lib.utils.find_resource')
300+
@mock.patch('openstackclient.image.v2.image.get_data_file')
301+
def test_image_create_from_volume(self, mock_get_data_f, mock_get_vol):
302+
303+
fake_vol_id = 'fake-volume-id'
304+
mock_get_data_f.return_value = (None, None)
305+
306+
class FakeVolume:
307+
id = fake_vol_id
308+
309+
mock_get_vol.return_value = FakeVolume()
310+
311+
arglist = [
312+
'--volume', fake_vol_id,
313+
self.new_image.name,
314+
]
315+
verifylist = [
316+
('name', self.new_image.name),
317+
]
318+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
319+
320+
columns, data = self.cmd.take_action(parsed_args)
321+
322+
self.volumes_mock.upload_to_image.assert_called_with(
323+
fake_vol_id,
324+
False,
325+
self.new_image.name,
326+
'bare',
327+
'raw'
328+
)
329+
330+
@mock.patch('osc_lib.utils.find_resource')
331+
@mock.patch('openstackclient.image.v2.image.get_data_file')
332+
def test_image_create_from_volume_fail(self, mock_get_data_f,
333+
mock_get_vol):
334+
335+
fake_vol_id = 'fake-volume-id'
336+
mock_get_data_f.return_value = (None, None)
337+
338+
class FakeVolume:
339+
id = fake_vol_id
340+
341+
mock_get_vol.return_value = FakeVolume()
342+
343+
arglist = [
344+
'--volume', fake_vol_id,
345+
self.new_image.name,
346+
'--public'
347+
]
348+
verifylist = [
349+
('name', self.new_image.name),
350+
]
351+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
352+
353+
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
354+
parsed_args)
355+
356+
@mock.patch('osc_lib.utils.find_resource')
357+
@mock.patch('openstackclient.image.v2.image.get_data_file')
358+
def test_image_create_from_volume_v31(self, mock_get_data_f,
359+
mock_get_vol):
360+
361+
self.app.client_manager.volume.api_version = (
362+
api_versions.APIVersion('3.1'))
363+
364+
fake_vol_id = 'fake-volume-id'
365+
mock_get_data_f.return_value = (None, None)
366+
367+
class FakeVolume:
368+
id = fake_vol_id
369+
370+
mock_get_vol.return_value = FakeVolume()
371+
372+
arglist = [
373+
'--volume', fake_vol_id,
374+
self.new_image.name,
375+
'--public'
376+
]
377+
verifylist = [
378+
('name', self.new_image.name),
379+
]
380+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
381+
382+
columns, data = self.cmd.take_action(parsed_args)
383+
384+
self.volumes_mock.upload_to_image.assert_called_with(
385+
fake_vol_id,
386+
False,
387+
self.new_image.name,
388+
'bare',
389+
'raw',
390+
visibility='public',
391+
protected=False
392+
)
393+
290394

291395
class TestAddProjectToImage(TestImage):
292396

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed create image from volume command. If user wants to
5+
pass ``visibility`` and ``protected`` fields, they need to
6+
specify volume microversion 3.1 or greater by passing
7+
``os-volume-api-version 3.1`` with the command.

0 commit comments

Comments
 (0)