python-openstackclient 8.0.0__py3-none-any.whl → 8.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openstackclient/api/compute_v2.py +2 -2
- openstackclient/api/volume_v2.py +60 -0
- openstackclient/api/volume_v3.py +60 -0
- openstackclient/compute/client.py +5 -0
- openstackclient/compute/v2/console.py +7 -0
- openstackclient/compute/v2/console_connection.py +48 -0
- openstackclient/compute/v2/flavor.py +14 -1
- openstackclient/compute/v2/keypair.py +10 -3
- openstackclient/compute/v2/server.py +76 -13
- openstackclient/compute/v2/server_event.py +1 -1
- openstackclient/identity/common.py +85 -11
- openstackclient/identity/v3/application_credential.py +88 -87
- openstackclient/identity/v3/domain.py +67 -49
- openstackclient/identity/v3/group.py +113 -68
- openstackclient/identity/v3/project.py +42 -20
- openstackclient/identity/v3/role.py +7 -2
- openstackclient/identity/v3/user.py +38 -5
- openstackclient/image/client.py +5 -0
- openstackclient/image/v1/image.py +16 -1
- openstackclient/image/v2/cache.py +10 -6
- openstackclient/image/v2/image.py +59 -12
- openstackclient/image/v2/metadef_objects.py +8 -2
- openstackclient/image/v2/metadef_properties.py +9 -2
- openstackclient/network/client.py +0 -6
- openstackclient/network/v2/floating_ip.py +58 -29
- openstackclient/network/v2/network_qos_rule.py +3 -11
- openstackclient/network/v2/port.py +16 -0
- openstackclient/network/v2/router.py +1 -1
- openstackclient/network/v2/security_group.py +49 -7
- openstackclient/network/v2/security_group_rule.py +18 -1
- openstackclient/shell.py +1 -1
- openstackclient/tests/functional/base.py +5 -1
- openstackclient/tests/functional/compute/v2/test_keypair.py +41 -5
- openstackclient/tests/functional/identity/v3/test_access_rule.py +1 -1
- openstackclient/tests/functional/identity/v3/test_application_credential.py +7 -7
- openstackclient/tests/functional/image/v2/test_image.py +36 -14
- openstackclient/tests/functional/volume/v2/test_volume.py +1 -1
- openstackclient/tests/functional/volume/v3/test_volume.py +2 -2
- openstackclient/tests/unit/api/test_volume_v2.py +124 -0
- openstackclient/tests/unit/api/test_volume_v3.py +124 -0
- openstackclient/tests/unit/compute/v2/fakes.py +81 -305
- openstackclient/tests/unit/compute/v2/test_console.py +18 -1
- openstackclient/tests/unit/compute/v2/test_console_connection.py +72 -0
- openstackclient/tests/unit/compute/v2/test_flavor.py +160 -175
- openstackclient/tests/unit/compute/v2/test_keypair.py +12 -5
- openstackclient/tests/unit/compute/v2/test_server.py +211 -97
- openstackclient/tests/unit/compute/v2/test_server_backup.py +32 -71
- openstackclient/tests/unit/compute/v2/test_server_event.py +2 -2
- openstackclient/tests/unit/compute/v2/test_server_image.py +33 -72
- openstackclient/tests/unit/compute/v2/test_server_migration.py +4 -4
- openstackclient/tests/unit/identity/v3/test_application_credential.py +93 -65
- openstackclient/tests/unit/identity/v3/test_domain.py +117 -107
- openstackclient/tests/unit/identity/v3/test_group.py +353 -202
- openstackclient/tests/unit/identity/v3/test_project.py +46 -53
- openstackclient/tests/unit/identity/v3/test_role.py +2 -8
- openstackclient/tests/unit/identity/v3/test_user.py +86 -6
- openstackclient/tests/unit/image/v1/test_image.py +55 -9
- openstackclient/tests/unit/image/v2/test_image.py +128 -58
- openstackclient/tests/unit/image/v2/test_metadef_objects.py +22 -0
- openstackclient/tests/unit/image/v2/test_metadef_properties.py +24 -10
- openstackclient/tests/unit/network/v2/fakes.py +406 -485
- openstackclient/tests/unit/network/v2/test_floating_ip_network.py +13 -19
- openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py +2 -2
- openstackclient/tests/unit/network/v2/test_ndp_proxy.py +3 -5
- openstackclient/tests/unit/network/v2/test_network.py +4 -4
- openstackclient/tests/unit/network/v2/test_network_agent.py +15 -29
- openstackclient/tests/unit/network/v2/test_network_qos_policy.py +16 -19
- openstackclient/tests/unit/network/v2/test_network_qos_rule.py +79 -152
- openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py +4 -6
- openstackclient/tests/unit/network/v2/test_network_rbac.py +2 -2
- openstackclient/tests/unit/network/v2/test_port.py +57 -17
- openstackclient/tests/unit/network/v2/test_router.py +73 -57
- openstackclient/tests/unit/network/v2/test_security_group_network.py +31 -27
- openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +1 -3
- openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +82 -39
- openstackclient/tests/unit/volume/v2/fakes.py +1 -2
- openstackclient/tests/unit/volume/v2/test_service.py +57 -91
- openstackclient/tests/unit/volume/v2/test_volume.py +466 -410
- openstackclient/tests/unit/volume/v2/test_volume_backup.py +141 -148
- openstackclient/tests/unit/volume/v2/test_volume_snapshot.py +293 -283
- openstackclient/tests/unit/volume/v3/test_block_storage_log_level.py +61 -71
- openstackclient/tests/unit/volume/v3/test_service.py +221 -141
- openstackclient/tests/unit/volume/v3/test_volume.py +569 -534
- openstackclient/tests/unit/volume/v3/test_volume_attachment.py +1 -1
- openstackclient/tests/unit/volume/v3/test_volume_backup.py +198 -203
- openstackclient/tests/unit/volume/v3/test_volume_snapshot.py +682 -47
- openstackclient/volume/v2/service.py +41 -38
- openstackclient/volume/v2/volume.py +140 -88
- openstackclient/volume/v2/volume_backup.py +9 -3
- openstackclient/volume/v2/volume_snapshot.py +121 -84
- openstackclient/volume/v3/block_storage_log_level.py +22 -28
- openstackclient/volume/v3/service.py +105 -14
- openstackclient/volume/v3/volume.py +287 -99
- openstackclient/volume/v3/volume_backup.py +24 -19
- openstackclient/volume/v3/volume_group.py +1 -1
- openstackclient/volume/v3/volume_snapshot.py +485 -10
- {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/AUTHORS +13 -0
- python_openstackclient-8.2.0.dist-info/METADATA +264 -0
- {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/RECORD +104 -98
- {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/entry_points.txt +7 -6
- python_openstackclient-8.2.0.dist-info/pbr.json +1 -0
- python_openstackclient-8.0.0.dist-info/METADATA +0 -166
- python_openstackclient-8.0.0.dist-info/pbr.json +0 -1
- {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/LICENSE +0 -0
- {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/WHEEL +0 -0
- {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/top_level.txt +0 -0
|
@@ -18,8 +18,10 @@ import argparse
|
|
|
18
18
|
import copy
|
|
19
19
|
import functools
|
|
20
20
|
import logging
|
|
21
|
+
import typing as ty
|
|
21
22
|
|
|
22
23
|
from cliff import columns as cliff_columns
|
|
24
|
+
from openstack.block_storage.v3 import volume as _volume
|
|
23
25
|
from openstack import exceptions as sdk_exceptions
|
|
24
26
|
from openstack import utils as sdk_utils
|
|
25
27
|
from osc_lib.cli import format_columns
|
|
@@ -28,10 +30,10 @@ from osc_lib.command import command
|
|
|
28
30
|
from osc_lib import exceptions
|
|
29
31
|
from osc_lib import utils
|
|
30
32
|
|
|
33
|
+
from openstackclient.api import volume_v3
|
|
31
34
|
from openstackclient.common import pagination
|
|
32
35
|
from openstackclient.i18n import _
|
|
33
36
|
from openstackclient.identity import common as identity_common
|
|
34
|
-
from openstackclient.volume.v2 import volume as volume_v2
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
LOG = logging.getLogger(__name__)
|
|
@@ -91,7 +93,53 @@ class AttachmentsColumn(cliff_columns.FormattableColumn):
|
|
|
91
93
|
return msg
|
|
92
94
|
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
def _format_volume(volume: _volume.Volume) -> dict[str, ty.Any]:
|
|
97
|
+
# Some columns returned by openstacksdk should not be shown because they're
|
|
98
|
+
# either irrelevant or duplicates
|
|
99
|
+
ignored_columns = {
|
|
100
|
+
# computed columns
|
|
101
|
+
'location',
|
|
102
|
+
# create-only columns
|
|
103
|
+
'OS-SCH-HNT:scheduler_hints',
|
|
104
|
+
'imageRef',
|
|
105
|
+
# removed columns
|
|
106
|
+
'os-volume-replication:driver_data',
|
|
107
|
+
'os-volume-replication:extended_status',
|
|
108
|
+
# unnecessary columns
|
|
109
|
+
'links',
|
|
110
|
+
}
|
|
111
|
+
optional_columns = {
|
|
112
|
+
# only present if part of a consistency group
|
|
113
|
+
'consistencygroup_id',
|
|
114
|
+
# only present if the volume is encrypted
|
|
115
|
+
'encryption_key_id',
|
|
116
|
+
# only present if there are image properties associated
|
|
117
|
+
'volume_image_metadata',
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
info = volume.to_dict(original_names=True)
|
|
121
|
+
data = {}
|
|
122
|
+
for key, value in info.items():
|
|
123
|
+
if key in ignored_columns:
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
if key in optional_columns:
|
|
127
|
+
if info[key] is None:
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
data[key] = value
|
|
131
|
+
|
|
132
|
+
data.update(
|
|
133
|
+
{
|
|
134
|
+
'properties': format_columns.DictColumn(data.pop('metadata')),
|
|
135
|
+
'type': data.pop('volume_type'),
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return data
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class CreateVolume(command.ShowOne):
|
|
95
143
|
_description = _("Create new volume")
|
|
96
144
|
|
|
97
145
|
@staticmethod
|
|
@@ -117,8 +165,48 @@ class CreateVolume(volume_v2.CreateVolume):
|
|
|
117
165
|
raise exceptions.CommandError(msg)
|
|
118
166
|
|
|
119
167
|
def get_parser(self, prog_name):
|
|
120
|
-
parser
|
|
121
|
-
|
|
168
|
+
parser = super().get_parser(prog_name)
|
|
169
|
+
parser.add_argument(
|
|
170
|
+
"name",
|
|
171
|
+
metavar="<name>",
|
|
172
|
+
nargs="?",
|
|
173
|
+
help=_("Volume name"),
|
|
174
|
+
)
|
|
175
|
+
parser.add_argument(
|
|
176
|
+
"--size",
|
|
177
|
+
metavar="<size>",
|
|
178
|
+
type=int,
|
|
179
|
+
help=_(
|
|
180
|
+
"Volume size in GB (required unless --snapshot or "
|
|
181
|
+
"--source specified)"
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
"--type",
|
|
186
|
+
metavar="<volume-type>",
|
|
187
|
+
help=_("Set the type of volume"),
|
|
188
|
+
)
|
|
189
|
+
source_group = parser.add_mutually_exclusive_group()
|
|
190
|
+
source_group.add_argument(
|
|
191
|
+
"--image",
|
|
192
|
+
metavar="<image>",
|
|
193
|
+
help=_("Use <image> as source of volume (name or ID)"),
|
|
194
|
+
)
|
|
195
|
+
source_group.add_argument(
|
|
196
|
+
"--snapshot",
|
|
197
|
+
metavar="<snapshot>",
|
|
198
|
+
help=_("Use <snapshot> as source of volume (name or ID)"),
|
|
199
|
+
)
|
|
200
|
+
source_group.add_argument(
|
|
201
|
+
"--source",
|
|
202
|
+
metavar="<volume>",
|
|
203
|
+
help=_("Volume to clone (name or ID)"),
|
|
204
|
+
)
|
|
205
|
+
source_group.add_argument(
|
|
206
|
+
"--source-replicated",
|
|
207
|
+
metavar="<replicated-volume>",
|
|
208
|
+
help=argparse.SUPPRESS,
|
|
209
|
+
)
|
|
122
210
|
source_group.add_argument(
|
|
123
211
|
"--backup",
|
|
124
212
|
metavar="<backup>",
|
|
@@ -138,6 +226,72 @@ class CreateVolume(volume_v2.CreateVolume):
|
|
|
138
226
|
"--remote-source source-id=test_id')"
|
|
139
227
|
),
|
|
140
228
|
)
|
|
229
|
+
parser.add_argument(
|
|
230
|
+
"--description",
|
|
231
|
+
metavar="<description>",
|
|
232
|
+
help=_("Volume description"),
|
|
233
|
+
)
|
|
234
|
+
parser.add_argument(
|
|
235
|
+
"--availability-zone",
|
|
236
|
+
metavar="<availability-zone>",
|
|
237
|
+
help=_("Create volume in <availability-zone>"),
|
|
238
|
+
)
|
|
239
|
+
parser.add_argument(
|
|
240
|
+
"--consistency-group",
|
|
241
|
+
metavar="consistency-group>",
|
|
242
|
+
help=_("Consistency group where the new volume belongs to"),
|
|
243
|
+
)
|
|
244
|
+
parser.add_argument(
|
|
245
|
+
"--property",
|
|
246
|
+
metavar="<key=value>",
|
|
247
|
+
action=parseractions.KeyValueAction,
|
|
248
|
+
dest="properties",
|
|
249
|
+
help=_(
|
|
250
|
+
"Set a property to this volume "
|
|
251
|
+
"(repeat option to set multiple properties)"
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
parser.add_argument(
|
|
255
|
+
"--hint",
|
|
256
|
+
metavar="<key=value>",
|
|
257
|
+
action=KeyValueHintAction,
|
|
258
|
+
help=_(
|
|
259
|
+
"Arbitrary scheduler hint key-value pairs to help creating "
|
|
260
|
+
"a volume. Repeat the option to set multiple hints. "
|
|
261
|
+
"'same_host' and 'different_host' get values appended when "
|
|
262
|
+
"repeated, all other keys take the last given value"
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
bootable_group = parser.add_mutually_exclusive_group()
|
|
266
|
+
bootable_group.add_argument(
|
|
267
|
+
"--bootable",
|
|
268
|
+
action="store_true",
|
|
269
|
+
dest="bootable",
|
|
270
|
+
default=None,
|
|
271
|
+
help=_("Mark volume as bootable"),
|
|
272
|
+
)
|
|
273
|
+
bootable_group.add_argument(
|
|
274
|
+
"--non-bootable",
|
|
275
|
+
action="store_false",
|
|
276
|
+
dest="bootable",
|
|
277
|
+
default=None,
|
|
278
|
+
help=_("Mark volume as non-bootable (default)"),
|
|
279
|
+
)
|
|
280
|
+
readonly_group = parser.add_mutually_exclusive_group()
|
|
281
|
+
readonly_group.add_argument(
|
|
282
|
+
"--read-only",
|
|
283
|
+
action="store_true",
|
|
284
|
+
dest="read_only",
|
|
285
|
+
default=None,
|
|
286
|
+
help=_("Set volume to read-only access mode"),
|
|
287
|
+
)
|
|
288
|
+
readonly_group.add_argument(
|
|
289
|
+
"--read-write",
|
|
290
|
+
action="store_false",
|
|
291
|
+
dest="read_only",
|
|
292
|
+
default=None,
|
|
293
|
+
help=_("Set volume to read-write access mode (default)"),
|
|
294
|
+
)
|
|
141
295
|
parser.add_argument(
|
|
142
296
|
"--host",
|
|
143
297
|
metavar="<host>",
|
|
@@ -160,15 +314,14 @@ class CreateVolume(volume_v2.CreateVolume):
|
|
|
160
314
|
return parser
|
|
161
315
|
|
|
162
316
|
def take_action(self, parsed_args):
|
|
163
|
-
|
|
317
|
+
self._check_size_arg(parsed_args)
|
|
164
318
|
# size is validated in the above call to
|
|
165
319
|
# _check_size_arg where we check that size
|
|
166
320
|
# should be passed if we are not creating a
|
|
167
321
|
# volume from snapshot, backup or source volume
|
|
168
322
|
size = parsed_args.size
|
|
169
323
|
|
|
170
|
-
|
|
171
|
-
volume_client = self.app.client_manager.volume
|
|
324
|
+
volume_client = self.app.client_manager.sdk_connection.volume
|
|
172
325
|
image_client = self.app.client_manager.image
|
|
173
326
|
|
|
174
327
|
if (
|
|
@@ -180,8 +333,8 @@ class CreateVolume(volume_v2.CreateVolume):
|
|
|
180
333
|
)
|
|
181
334
|
raise exceptions.CommandError(msg)
|
|
182
335
|
|
|
183
|
-
if parsed_args.backup and not (
|
|
184
|
-
volume_client
|
|
336
|
+
if parsed_args.backup and not sdk_utils.supports_microversion(
|
|
337
|
+
volume_client, '3.47'
|
|
185
338
|
):
|
|
186
339
|
msg = _(
|
|
187
340
|
"--os-volume-api-version 3.47 or greater is required "
|
|
@@ -194,8 +347,7 @@ class CreateVolume(volume_v2.CreateVolume):
|
|
|
194
347
|
parsed_args.size
|
|
195
348
|
or parsed_args.consistency_group
|
|
196
349
|
or parsed_args.hint
|
|
197
|
-
or parsed_args.read_only
|
|
198
|
-
or parsed_args.read_write
|
|
350
|
+
or parsed_args.read_only is not None
|
|
199
351
|
):
|
|
200
352
|
msg = _(
|
|
201
353
|
"The --size, --consistency-group, --hint, --read-only "
|
|
@@ -204,9 +356,7 @@ class CreateVolume(volume_v2.CreateVolume):
|
|
|
204
356
|
)
|
|
205
357
|
raise exceptions.CommandError(msg)
|
|
206
358
|
if parsed_args.cluster:
|
|
207
|
-
if not sdk_utils.supports_microversion(
|
|
208
|
-
volume_client_sdk, '3.16'
|
|
209
|
-
):
|
|
359
|
+
if not sdk_utils.supports_microversion(volume_client, '3.16'):
|
|
210
360
|
msg = _(
|
|
211
361
|
"--os-volume-api-version 3.16 or greater is required "
|
|
212
362
|
"to support the cluster parameter."
|
|
@@ -224,7 +374,7 @@ class CreateVolume(volume_v2.CreateVolume):
|
|
|
224
374
|
"manage a volume."
|
|
225
375
|
)
|
|
226
376
|
raise exceptions.CommandError(msg)
|
|
227
|
-
volume =
|
|
377
|
+
volume = volume_client.manage_volume(
|
|
228
378
|
host=parsed_args.host,
|
|
229
379
|
cluster=parsed_args.cluster,
|
|
230
380
|
ref=parsed_args.remote_source,
|
|
@@ -232,24 +382,25 @@ class CreateVolume(volume_v2.CreateVolume):
|
|
|
232
382
|
description=parsed_args.description,
|
|
233
383
|
volume_type=parsed_args.type,
|
|
234
384
|
availability_zone=parsed_args.availability_zone,
|
|
235
|
-
metadata=parsed_args.
|
|
385
|
+
metadata=parsed_args.properties,
|
|
236
386
|
bootable=parsed_args.bootable,
|
|
237
387
|
)
|
|
238
|
-
|
|
388
|
+
data = _format_volume(volume)
|
|
389
|
+
return zip(*sorted(data.items()))
|
|
239
390
|
|
|
240
391
|
source_volume = None
|
|
241
392
|
if parsed_args.source:
|
|
242
|
-
source_volume_obj =
|
|
243
|
-
|
|
393
|
+
source_volume_obj = volume_client.find_volume(
|
|
394
|
+
parsed_args.source, ignore_missing=False
|
|
244
395
|
)
|
|
245
396
|
source_volume = source_volume_obj.id
|
|
246
397
|
size = max(size or 0, source_volume_obj.size)
|
|
247
398
|
|
|
248
399
|
consistency_group = None
|
|
249
400
|
if parsed_args.consistency_group:
|
|
250
|
-
consistency_group =
|
|
251
|
-
volume_client
|
|
252
|
-
)
|
|
401
|
+
consistency_group = volume_v3.find_consistency_group(
|
|
402
|
+
volume_client, parsed_args.consistency_group
|
|
403
|
+
)['id']
|
|
253
404
|
|
|
254
405
|
image = None
|
|
255
406
|
if parsed_args.image:
|
|
@@ -259,8 +410,8 @@ class CreateVolume(volume_v2.CreateVolume):
|
|
|
259
410
|
|
|
260
411
|
snapshot = None
|
|
261
412
|
if parsed_args.snapshot:
|
|
262
|
-
snapshot_obj =
|
|
263
|
-
|
|
413
|
+
snapshot_obj = volume_client.find_snapshot(
|
|
414
|
+
parsed_args.snapshot, ignore_missing=False
|
|
264
415
|
)
|
|
265
416
|
snapshot = snapshot_obj.id
|
|
266
417
|
# Cinder requires a value for size when creating a volume
|
|
@@ -273,39 +424,39 @@ class CreateVolume(volume_v2.CreateVolume):
|
|
|
273
424
|
|
|
274
425
|
backup = None
|
|
275
426
|
if parsed_args.backup:
|
|
276
|
-
backup_obj =
|
|
277
|
-
|
|
427
|
+
backup_obj = volume_client.find_backup(
|
|
428
|
+
parsed_args.backup, ignore_missing=False
|
|
278
429
|
)
|
|
279
430
|
backup = backup_obj.id
|
|
280
431
|
# As above
|
|
281
432
|
size = max(size or 0, backup_obj.size)
|
|
282
433
|
|
|
283
|
-
volume = volume_client.
|
|
434
|
+
volume = volume_client.create_volume(
|
|
284
435
|
size=size,
|
|
285
436
|
snapshot_id=snapshot,
|
|
286
437
|
name=parsed_args.name,
|
|
287
438
|
description=parsed_args.description,
|
|
288
439
|
volume_type=parsed_args.type,
|
|
289
440
|
availability_zone=parsed_args.availability_zone,
|
|
290
|
-
metadata=parsed_args.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
441
|
+
metadata=parsed_args.properties,
|
|
442
|
+
image_id=image,
|
|
443
|
+
source_volume_id=source_volume,
|
|
444
|
+
consistency_group_id=consistency_group,
|
|
294
445
|
scheduler_hints=parsed_args.hint,
|
|
295
446
|
backup_id=backup,
|
|
296
447
|
)
|
|
297
448
|
|
|
298
|
-
if parsed_args.bootable
|
|
449
|
+
if parsed_args.bootable is not None:
|
|
299
450
|
try:
|
|
300
451
|
if utils.wait_for_status(
|
|
301
|
-
volume_client.
|
|
452
|
+
volume_client.get_volume,
|
|
302
453
|
volume.id,
|
|
303
454
|
success_status=['available'],
|
|
304
455
|
error_status=['error'],
|
|
305
456
|
sleep_time=1,
|
|
306
457
|
):
|
|
307
|
-
volume_client.
|
|
308
|
-
volume
|
|
458
|
+
volume_client.set_volume_bootable_status(
|
|
459
|
+
volume, parsed_args.bootable
|
|
309
460
|
)
|
|
310
461
|
else:
|
|
311
462
|
msg = _(
|
|
@@ -314,17 +465,18 @@ class CreateVolume(volume_v2.CreateVolume):
|
|
|
314
465
|
raise exceptions.CommandError(msg)
|
|
315
466
|
except Exception as e:
|
|
316
467
|
LOG.error(_("Failed to set volume bootable property: %s"), e)
|
|
317
|
-
|
|
468
|
+
|
|
469
|
+
if parsed_args.read_only is not None:
|
|
318
470
|
try:
|
|
319
471
|
if utils.wait_for_status(
|
|
320
|
-
volume_client.
|
|
472
|
+
volume_client.get_volume,
|
|
321
473
|
volume.id,
|
|
322
474
|
success_status=['available'],
|
|
323
475
|
error_status=['error'],
|
|
324
476
|
sleep_time=1,
|
|
325
477
|
):
|
|
326
|
-
volume_client.
|
|
327
|
-
volume
|
|
478
|
+
volume_client.set_volume_readonly(
|
|
479
|
+
volume, parsed_args.read_only
|
|
328
480
|
)
|
|
329
481
|
else:
|
|
330
482
|
msg = _(
|
|
@@ -338,24 +490,37 @@ class CreateVolume(volume_v2.CreateVolume):
|
|
|
338
490
|
e,
|
|
339
491
|
)
|
|
340
492
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
{
|
|
344
|
-
'properties': format_columns.DictColumn(
|
|
345
|
-
volume._info.pop('metadata')
|
|
346
|
-
),
|
|
347
|
-
'type': volume._info.pop('volume_type'),
|
|
348
|
-
}
|
|
349
|
-
)
|
|
350
|
-
volume._info.pop("links", None)
|
|
351
|
-
return zip(*sorted(volume._info.items()))
|
|
493
|
+
data = _format_volume(volume)
|
|
494
|
+
return zip(*sorted(data.items()))
|
|
352
495
|
|
|
353
496
|
|
|
354
|
-
class DeleteVolume(
|
|
497
|
+
class DeleteVolume(command.Command):
|
|
355
498
|
_description = _("Delete volume(s)")
|
|
356
499
|
|
|
357
500
|
def get_parser(self, prog_name):
|
|
358
501
|
parser = super().get_parser(prog_name)
|
|
502
|
+
parser.add_argument(
|
|
503
|
+
"volumes",
|
|
504
|
+
metavar="<volume>",
|
|
505
|
+
nargs="+",
|
|
506
|
+
help=_("Volume(s) to delete (name or ID)"),
|
|
507
|
+
)
|
|
508
|
+
group = parser.add_mutually_exclusive_group()
|
|
509
|
+
group.add_argument(
|
|
510
|
+
"--force",
|
|
511
|
+
action="store_true",
|
|
512
|
+
help=_(
|
|
513
|
+
"Attempt forced removal of volume(s), regardless of state "
|
|
514
|
+
"(defaults to False)"
|
|
515
|
+
),
|
|
516
|
+
)
|
|
517
|
+
group.add_argument(
|
|
518
|
+
"--purge",
|
|
519
|
+
action="store_true",
|
|
520
|
+
help=_(
|
|
521
|
+
"Remove any snapshots along with volume(s) (defaults to False)"
|
|
522
|
+
),
|
|
523
|
+
)
|
|
359
524
|
parser.add_argument(
|
|
360
525
|
'--remote',
|
|
361
526
|
action='store_true',
|
|
@@ -364,8 +529,7 @@ class DeleteVolume(volume_v2.DeleteVolume):
|
|
|
364
529
|
return parser
|
|
365
530
|
|
|
366
531
|
def take_action(self, parsed_args):
|
|
367
|
-
volume_client = self.app.client_manager.volume
|
|
368
|
-
volume_client_sdk = self.app.client_manager.sdk_connection.volume
|
|
532
|
+
volume_client = self.app.client_manager.sdk_connection.volume
|
|
369
533
|
result = 0
|
|
370
534
|
|
|
371
535
|
if parsed_args.remote and (parsed_args.force or parsed_args.purge):
|
|
@@ -375,16 +539,18 @@ class DeleteVolume(volume_v2.DeleteVolume):
|
|
|
375
539
|
)
|
|
376
540
|
raise exceptions.CommandError(msg)
|
|
377
541
|
|
|
378
|
-
for
|
|
542
|
+
for volume in parsed_args.volumes:
|
|
379
543
|
try:
|
|
380
|
-
volume_obj =
|
|
544
|
+
volume_obj = volume_client.find_volume(
|
|
545
|
+
volume, ignore_missing=False
|
|
546
|
+
)
|
|
381
547
|
if parsed_args.remote:
|
|
382
|
-
|
|
383
|
-
elif parsed_args.force:
|
|
384
|
-
volume_client.volumes.force_delete(volume_obj.id)
|
|
548
|
+
volume_client.unmanage_volume(volume_obj.id)
|
|
385
549
|
else:
|
|
386
|
-
volume_client.
|
|
387
|
-
volume_obj.id,
|
|
550
|
+
volume_client.delete_volume(
|
|
551
|
+
volume_obj.id,
|
|
552
|
+
force=parsed_args.force,
|
|
553
|
+
cascade=parsed_args.purge,
|
|
388
554
|
)
|
|
389
555
|
except Exception as e:
|
|
390
556
|
result += 1
|
|
@@ -393,7 +559,7 @@ class DeleteVolume(volume_v2.DeleteVolume):
|
|
|
393
559
|
"Failed to delete volume with "
|
|
394
560
|
"name or ID '%(volume)s': %(e)s"
|
|
395
561
|
),
|
|
396
|
-
{'volume':
|
|
562
|
+
{'volume': volume, 'e': e},
|
|
397
563
|
)
|
|
398
564
|
|
|
399
565
|
if result > 0:
|
|
@@ -432,6 +598,16 @@ class ListVolume(command.Lister):
|
|
|
432
598
|
metavar='<status>',
|
|
433
599
|
help=_('Filter results by status'),
|
|
434
600
|
)
|
|
601
|
+
parser.add_argument(
|
|
602
|
+
'--property',
|
|
603
|
+
metavar='<key=value>',
|
|
604
|
+
action=parseractions.KeyValueAction,
|
|
605
|
+
dest='properties',
|
|
606
|
+
help=_(
|
|
607
|
+
'Filter by a property on the volume list '
|
|
608
|
+
'(repeat option to filter by multiple properties) '
|
|
609
|
+
),
|
|
610
|
+
)
|
|
435
611
|
parser.add_argument(
|
|
436
612
|
'--all-projects',
|
|
437
613
|
action='store_true',
|
|
@@ -500,6 +676,7 @@ class ListVolume(command.Lister):
|
|
|
500
676
|
'user_id': user_id,
|
|
501
677
|
'name': parsed_args.name,
|
|
502
678
|
'status': parsed_args.status,
|
|
679
|
+
'metadata': parsed_args.properties,
|
|
503
680
|
}
|
|
504
681
|
|
|
505
682
|
data = volume_client.volumes.list(
|
|
@@ -584,16 +761,19 @@ class MigrateVolume(command.Command):
|
|
|
584
761
|
"(possibly by another operation)"
|
|
585
762
|
),
|
|
586
763
|
)
|
|
764
|
+
# TODO(stephenfin): Add --cluster argument
|
|
587
765
|
return parser
|
|
588
766
|
|
|
589
767
|
def take_action(self, parsed_args):
|
|
590
|
-
volume_client = self.app.client_manager.volume
|
|
591
|
-
volume =
|
|
592
|
-
|
|
768
|
+
volume_client = self.app.client_manager.sdk_connection.volume
|
|
769
|
+
volume = volume_client.find_volume(
|
|
770
|
+
parsed_args.volume, ignore_missing=False
|
|
771
|
+
)
|
|
772
|
+
volume_client.migrate_volume(
|
|
593
773
|
volume.id,
|
|
594
|
-
parsed_args.host,
|
|
595
|
-
parsed_args.force_host_copy,
|
|
596
|
-
parsed_args.lock_volume,
|
|
774
|
+
host=parsed_args.host,
|
|
775
|
+
force_host_copy=parsed_args.force_host_copy,
|
|
776
|
+
lock_volume=parsed_args.lock_volume,
|
|
597
777
|
)
|
|
598
778
|
|
|
599
779
|
|
|
@@ -638,6 +818,7 @@ class SetVolume(command.Command):
|
|
|
638
818
|
'--property',
|
|
639
819
|
metavar='<key=value>',
|
|
640
820
|
action=parseractions.KeyValueAction,
|
|
821
|
+
dest='properties',
|
|
641
822
|
help=_(
|
|
642
823
|
'Set a property on this volume '
|
|
643
824
|
'(repeat option to set multiple properties)'
|
|
@@ -647,6 +828,7 @@ class SetVolume(command.Command):
|
|
|
647
828
|
'--image-property',
|
|
648
829
|
metavar='<key=value>',
|
|
649
830
|
action=parseractions.KeyValueAction,
|
|
831
|
+
dest='image_properties',
|
|
650
832
|
help=_(
|
|
651
833
|
'Set an image property on this volume '
|
|
652
834
|
'(repeat option to set multiple image properties)'
|
|
@@ -723,22 +905,30 @@ class SetVolume(command.Command):
|
|
|
723
905
|
bootable_group.add_argument(
|
|
724
906
|
"--bootable",
|
|
725
907
|
action="store_true",
|
|
908
|
+
dest="bootable",
|
|
909
|
+
default=None,
|
|
726
910
|
help=_("Mark volume as bootable"),
|
|
727
911
|
)
|
|
728
912
|
bootable_group.add_argument(
|
|
729
913
|
"--non-bootable",
|
|
730
|
-
action="
|
|
914
|
+
action="store_false",
|
|
915
|
+
dest="bootable",
|
|
916
|
+
default=None,
|
|
731
917
|
help=_("Mark volume as non-bootable"),
|
|
732
918
|
)
|
|
733
919
|
readonly_group = parser.add_mutually_exclusive_group()
|
|
734
920
|
readonly_group.add_argument(
|
|
735
921
|
"--read-only",
|
|
736
922
|
action="store_true",
|
|
923
|
+
dest="read_only",
|
|
924
|
+
default=None,
|
|
737
925
|
help=_("Set volume to read-only access mode"),
|
|
738
926
|
)
|
|
739
927
|
readonly_group.add_argument(
|
|
740
928
|
"--read-write",
|
|
741
|
-
action="
|
|
929
|
+
action="store_false",
|
|
930
|
+
dest="read_only",
|
|
931
|
+
default=None,
|
|
742
932
|
help=_("Set volume to read-write access mode"),
|
|
743
933
|
)
|
|
744
934
|
return parser
|
|
@@ -796,28 +986,31 @@ class SetVolume(command.Command):
|
|
|
796
986
|
LOG.error(_("Failed to clean volume properties: %s"), e)
|
|
797
987
|
result += 1
|
|
798
988
|
|
|
799
|
-
if parsed_args.
|
|
989
|
+
if parsed_args.properties:
|
|
800
990
|
try:
|
|
801
991
|
volume_client.volumes.set_metadata(
|
|
802
|
-
volume.id, parsed_args.
|
|
992
|
+
volume.id, parsed_args.properties
|
|
803
993
|
)
|
|
804
994
|
except Exception as e:
|
|
805
|
-
LOG.error(_("Failed to set volume
|
|
995
|
+
LOG.error(_("Failed to set volume properties: %s"), e)
|
|
806
996
|
result += 1
|
|
807
|
-
|
|
997
|
+
|
|
998
|
+
if parsed_args.image_properties:
|
|
808
999
|
try:
|
|
809
1000
|
volume_client.volumes.set_image_metadata(
|
|
810
|
-
volume.id, parsed_args.
|
|
1001
|
+
volume.id, parsed_args.image_properties
|
|
811
1002
|
)
|
|
812
1003
|
except Exception as e:
|
|
813
|
-
LOG.error(_("Failed to set image
|
|
1004
|
+
LOG.error(_("Failed to set image properties: %s"), e)
|
|
814
1005
|
result += 1
|
|
1006
|
+
|
|
815
1007
|
if parsed_args.state:
|
|
816
1008
|
try:
|
|
817
1009
|
volume_client.volumes.reset_state(volume.id, parsed_args.state)
|
|
818
1010
|
except Exception as e:
|
|
819
1011
|
LOG.error(_("Failed to set volume state: %s"), e)
|
|
820
1012
|
result += 1
|
|
1013
|
+
|
|
821
1014
|
if parsed_args.attached:
|
|
822
1015
|
try:
|
|
823
1016
|
volume_client.volumes.reset_state(
|
|
@@ -826,6 +1019,7 @@ class SetVolume(command.Command):
|
|
|
826
1019
|
except Exception as e:
|
|
827
1020
|
LOG.error(_("Failed to set volume attach-status: %s"), e)
|
|
828
1021
|
result += 1
|
|
1022
|
+
|
|
829
1023
|
if parsed_args.detached:
|
|
830
1024
|
try:
|
|
831
1025
|
volume_client.volumes.reset_state(
|
|
@@ -834,7 +1028,8 @@ class SetVolume(command.Command):
|
|
|
834
1028
|
except Exception as e:
|
|
835
1029
|
LOG.error(_("Failed to set volume attach-status: %s"), e)
|
|
836
1030
|
result += 1
|
|
837
|
-
|
|
1031
|
+
|
|
1032
|
+
if parsed_args.bootable is not None:
|
|
838
1033
|
try:
|
|
839
1034
|
volume_client.volumes.set_bootable(
|
|
840
1035
|
volume.id, parsed_args.bootable
|
|
@@ -842,7 +1037,8 @@ class SetVolume(command.Command):
|
|
|
842
1037
|
except Exception as e:
|
|
843
1038
|
LOG.error(_("Failed to set volume bootable property: %s"), e)
|
|
844
1039
|
result += 1
|
|
845
|
-
|
|
1040
|
+
|
|
1041
|
+
if parsed_args.read_only is not None:
|
|
846
1042
|
try:
|
|
847
1043
|
volume_client.volumes.update_readonly_flag(
|
|
848
1044
|
volume.id, parsed_args.read_only
|
|
@@ -853,6 +1049,7 @@ class SetVolume(command.Command):
|
|
|
853
1049
|
e,
|
|
854
1050
|
)
|
|
855
1051
|
result += 1
|
|
1052
|
+
|
|
856
1053
|
policy = parsed_args.migration_policy or parsed_args.retype_policy
|
|
857
1054
|
if parsed_args.type:
|
|
858
1055
|
# get the migration policy
|
|
@@ -919,24 +1116,13 @@ class ShowVolume(command.ShowOne):
|
|
|
919
1116
|
return parser
|
|
920
1117
|
|
|
921
1118
|
def take_action(self, parsed_args):
|
|
922
|
-
volume_client = self.app.client_manager.volume
|
|
923
|
-
volume =
|
|
924
|
-
|
|
925
|
-
# Special mapping for columns to make the output easier to read:
|
|
926
|
-
# 'metadata' --> 'properties'
|
|
927
|
-
# 'volume_type' --> 'type'
|
|
928
|
-
volume._info.update(
|
|
929
|
-
{
|
|
930
|
-
'properties': format_columns.DictColumn(
|
|
931
|
-
volume._info.pop('metadata')
|
|
932
|
-
),
|
|
933
|
-
'type': volume._info.pop('volume_type'),
|
|
934
|
-
},
|
|
1119
|
+
volume_client = self.app.client_manager.sdk_connection.volume
|
|
1120
|
+
volume = volume_client.find_volume(
|
|
1121
|
+
parsed_args.volume, ignore_missing=False
|
|
935
1122
|
)
|
|
936
1123
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
return zip(*sorted(volume._info.items()))
|
|
1124
|
+
data = _format_volume(volume)
|
|
1125
|
+
return zip(*sorted(data.items()))
|
|
940
1126
|
|
|
941
1127
|
|
|
942
1128
|
class UnsetVolume(command.Command):
|
|
@@ -953,6 +1139,7 @@ class UnsetVolume(command.Command):
|
|
|
953
1139
|
'--property',
|
|
954
1140
|
metavar='<key>',
|
|
955
1141
|
action='append',
|
|
1142
|
+
dest='properties',
|
|
956
1143
|
help=_(
|
|
957
1144
|
'Remove a property from volume '
|
|
958
1145
|
'(repeat option to remove multiple properties)'
|
|
@@ -962,6 +1149,7 @@ class UnsetVolume(command.Command):
|
|
|
962
1149
|
'--image-property',
|
|
963
1150
|
metavar='<key>',
|
|
964
1151
|
action='append',
|
|
1152
|
+
dest='image_properties',
|
|
965
1153
|
help=_(
|
|
966
1154
|
'Remove an image property from volume '
|
|
967
1155
|
'(repeat option to remove multiple image properties)'
|
|
@@ -974,22 +1162,22 @@ class UnsetVolume(command.Command):
|
|
|
974
1162
|
volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
|
|
975
1163
|
|
|
976
1164
|
result = 0
|
|
977
|
-
if parsed_args.
|
|
1165
|
+
if parsed_args.properties:
|
|
978
1166
|
try:
|
|
979
1167
|
volume_client.volumes.delete_metadata(
|
|
980
|
-
volume.id, parsed_args.
|
|
1168
|
+
volume.id, parsed_args.properties
|
|
981
1169
|
)
|
|
982
1170
|
except Exception as e:
|
|
983
|
-
LOG.error(_("Failed to unset volume
|
|
1171
|
+
LOG.error(_("Failed to unset volume properties: %s"), e)
|
|
984
1172
|
result += 1
|
|
985
1173
|
|
|
986
|
-
if parsed_args.
|
|
1174
|
+
if parsed_args.image_properties:
|
|
987
1175
|
try:
|
|
988
1176
|
volume_client.volumes.delete_image_metadata(
|
|
989
|
-
volume.id, parsed_args.
|
|
1177
|
+
volume.id, parsed_args.image_properties
|
|
990
1178
|
)
|
|
991
1179
|
except Exception as e:
|
|
992
|
-
LOG.error(_("Failed to unset image
|
|
1180
|
+
LOG.error(_("Failed to unset image properties: %s"), e)
|
|
993
1181
|
result += 1
|
|
994
1182
|
|
|
995
1183
|
if result > 0:
|