Skip to content

Commit e6e0dbf

Browse files
committed
Add --volume option to image create command
Add ability to create an image from a volume. * Added --volume command to image create * Added --force option to image create * Added block to access volume manager in image create * Tests added for the volume option Change-Id: I3910a2b5e04acd0d15dd230747ba6ebca07aa316 Closes-Bug: #1207615
1 parent b7f673c commit e6e0dbf

2 files changed

Lines changed: 119 additions & 16 deletions

File tree

‎openstackclient/image/v1/image.py‎

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,19 @@ def get_parser(self, prog_name):
111111
help="Similar to --location, but this indicates that the image"
112112
" should immediately be copied from the data store",
113113
)
114+
parser.add_argument(
115+
"--volume",
116+
metavar="<volume>",
117+
help="Create the image from the specified volume",
118+
)
119+
parser.add_argument(
120+
"--force",
121+
dest='force',
122+
action='store_true',
123+
default=False,
124+
help="If the image is created from a volume, force creation of the"
125+
" image even if volume is in use.",
126+
)
114127
parser.add_argument(
115128
"--property",
116129
dest="properties",
@@ -162,7 +175,9 @@ def take_action(self, parsed_args):
162175
args.pop("variables")
163176

164177
if "location" not in args and "copy_from" not in args:
165-
if "file" in args:
178+
if "volume" in args:
179+
pass
180+
elif "file" in args:
166181
args["data"] = open(args.pop("file"), "rb")
167182
else:
168183
args["data"] = None
@@ -171,23 +186,35 @@ def take_action(self, parsed_args):
171186
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
172187
args["data"] = sys.stdin
173188

174-
image_client = self.app.client_manager.image
175-
try:
176-
image = utils.find_resource(
177-
image_client.images,
178-
parsed_args.name,
179-
)
180-
except exceptions.CommandError:
181-
# This is normal for a create or reserve (create w/o an image)
182-
image = image_client.images.create(**args)
189+
if "volume" in args:
190+
volume_client = self.app.client_manager.volume
191+
source_volume = utils.find_resource(volume_client.volumes,
192+
parsed_args.volume)
193+
response, body = volume_client.volumes.upload_to_image(
194+
source_volume,
195+
parsed_args.force,
196+
parsed_args.name,
197+
parsed_args.container_format,
198+
parsed_args.disk_format)
199+
info = body['os-volume_upload_image']
183200
else:
184-
# It must be an update
185-
# If an image is specified via --file, --location or --copy-from
186-
# let the API handle it
187-
image = image_client.images.update(image, **args)
201+
image_client = self.app.client_manager.image
202+
try:
203+
image = utils.find_resource(
204+
image_client.images,
205+
parsed_args.name,
206+
)
207+
except exceptions.CommandError:
208+
# This is normal for a create or reserve (create w/o an image)
209+
image = image_client.images.create(**args)
210+
else:
211+
# It must be an update
212+
# If an image is specified via --file, --location or
213+
# --copy-from let the API handle it
214+
image = image_client.images.update(image, **args)
188215

189-
info = {}
190-
info.update(image._info)
216+
info = {}
217+
info.update(image._info)
191218
return zip(*sorted(six.iteritems(info)))
192219

193220

‎openstackclient/tests/image/v1/test_image.py‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#
1515

1616
import copy
17+
import mock
1718

1819
from openstackclient.image.v1 import image
1920
from openstackclient.tests import fakes
@@ -30,6 +31,81 @@ def setUp(self):
3031
self.images_mock.reset_mock()
3132

3233

34+
class TestImageCreate(TestImage):
35+
36+
def setUp(self):
37+
super(TestImageCreate, self).setUp()
38+
self.images_mock.get.return_value = fakes.FakeResource(
39+
None,
40+
copy.deepcopy(image_fakes.IMAGE),
41+
loaded=True,
42+
)
43+
self.cmd = image.CreateImage(self.app, None)
44+
45+
def test_create_volume(self):
46+
arglist = [
47+
'--volume', 'volly',
48+
image_fakes.image_name,
49+
]
50+
verifylist = [
51+
('volume', 'volly'),
52+
('name', image_fakes.image_name),
53+
]
54+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
55+
self.app.client_manager.volume = mock.Mock()
56+
self.app.client_manager.volume.volumes = mock.Mock()
57+
volumes = self.app.client_manager.volume.volumes
58+
volumes.upload_to_image = mock.Mock()
59+
response = {"id": 'volume_id',
60+
"updated_at": 'updated_at',
61+
"status": 'uploading',
62+
"display_description": 'desc',
63+
"size": 'size',
64+
"volume_type": 'volume_type',
65+
"image_id": 'image1',
66+
"container_format": parsed_args.container_format,
67+
"disk_format": parsed_args.disk_format,
68+
"image_name": parsed_args.name}
69+
full_response = {"os-volume_upload_image": response}
70+
volumes.upload_to_image.return_value = (201, full_response)
71+
volume_resource = fakes.FakeResource(
72+
None,
73+
copy.deepcopy({'id': 'vol1', 'name': 'volly'}),
74+
loaded=True,
75+
)
76+
volumes.get.return_value = volume_resource
77+
results = self.cmd.take_action(parsed_args)
78+
volumes.upload_to_image.assert_called_with(
79+
volume_resource,
80+
False,
81+
image_fakes.image_name,
82+
'bare',
83+
'raw',
84+
)
85+
expects = [('container_format',
86+
'disk_format',
87+
'display_description',
88+
'id',
89+
'image_id',
90+
'image_name',
91+
'size',
92+
'status',
93+
'updated_at',
94+
'volume_type'),
95+
('bare',
96+
'raw',
97+
'desc',
98+
'volume_id',
99+
'image1',
100+
'graven',
101+
'size',
102+
'uploading',
103+
'updated_at',
104+
'volume_type')]
105+
for expected, result in zip(expects, results):
106+
self.assertEqual(expected, result)
107+
108+
33109
class TestImageDelete(TestImage):
34110

35111
def setUp(self):

0 commit comments

Comments
 (0)