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.
Files changed (106) hide show
  1. openstackclient/api/compute_v2.py +2 -2
  2. openstackclient/api/volume_v2.py +60 -0
  3. openstackclient/api/volume_v3.py +60 -0
  4. openstackclient/compute/client.py +5 -0
  5. openstackclient/compute/v2/console.py +7 -0
  6. openstackclient/compute/v2/console_connection.py +48 -0
  7. openstackclient/compute/v2/flavor.py +14 -1
  8. openstackclient/compute/v2/keypair.py +10 -3
  9. openstackclient/compute/v2/server.py +76 -13
  10. openstackclient/compute/v2/server_event.py +1 -1
  11. openstackclient/identity/common.py +85 -11
  12. openstackclient/identity/v3/application_credential.py +88 -87
  13. openstackclient/identity/v3/domain.py +67 -49
  14. openstackclient/identity/v3/group.py +113 -68
  15. openstackclient/identity/v3/project.py +42 -20
  16. openstackclient/identity/v3/role.py +7 -2
  17. openstackclient/identity/v3/user.py +38 -5
  18. openstackclient/image/client.py +5 -0
  19. openstackclient/image/v1/image.py +16 -1
  20. openstackclient/image/v2/cache.py +10 -6
  21. openstackclient/image/v2/image.py +59 -12
  22. openstackclient/image/v2/metadef_objects.py +8 -2
  23. openstackclient/image/v2/metadef_properties.py +9 -2
  24. openstackclient/network/client.py +0 -6
  25. openstackclient/network/v2/floating_ip.py +58 -29
  26. openstackclient/network/v2/network_qos_rule.py +3 -11
  27. openstackclient/network/v2/port.py +16 -0
  28. openstackclient/network/v2/router.py +1 -1
  29. openstackclient/network/v2/security_group.py +49 -7
  30. openstackclient/network/v2/security_group_rule.py +18 -1
  31. openstackclient/shell.py +1 -1
  32. openstackclient/tests/functional/base.py +5 -1
  33. openstackclient/tests/functional/compute/v2/test_keypair.py +41 -5
  34. openstackclient/tests/functional/identity/v3/test_access_rule.py +1 -1
  35. openstackclient/tests/functional/identity/v3/test_application_credential.py +7 -7
  36. openstackclient/tests/functional/image/v2/test_image.py +36 -14
  37. openstackclient/tests/functional/volume/v2/test_volume.py +1 -1
  38. openstackclient/tests/functional/volume/v3/test_volume.py +2 -2
  39. openstackclient/tests/unit/api/test_volume_v2.py +124 -0
  40. openstackclient/tests/unit/api/test_volume_v3.py +124 -0
  41. openstackclient/tests/unit/compute/v2/fakes.py +81 -305
  42. openstackclient/tests/unit/compute/v2/test_console.py +18 -1
  43. openstackclient/tests/unit/compute/v2/test_console_connection.py +72 -0
  44. openstackclient/tests/unit/compute/v2/test_flavor.py +160 -175
  45. openstackclient/tests/unit/compute/v2/test_keypair.py +12 -5
  46. openstackclient/tests/unit/compute/v2/test_server.py +211 -97
  47. openstackclient/tests/unit/compute/v2/test_server_backup.py +32 -71
  48. openstackclient/tests/unit/compute/v2/test_server_event.py +2 -2
  49. openstackclient/tests/unit/compute/v2/test_server_image.py +33 -72
  50. openstackclient/tests/unit/compute/v2/test_server_migration.py +4 -4
  51. openstackclient/tests/unit/identity/v3/test_application_credential.py +93 -65
  52. openstackclient/tests/unit/identity/v3/test_domain.py +117 -107
  53. openstackclient/tests/unit/identity/v3/test_group.py +353 -202
  54. openstackclient/tests/unit/identity/v3/test_project.py +46 -53
  55. openstackclient/tests/unit/identity/v3/test_role.py +2 -8
  56. openstackclient/tests/unit/identity/v3/test_user.py +86 -6
  57. openstackclient/tests/unit/image/v1/test_image.py +55 -9
  58. openstackclient/tests/unit/image/v2/test_image.py +128 -58
  59. openstackclient/tests/unit/image/v2/test_metadef_objects.py +22 -0
  60. openstackclient/tests/unit/image/v2/test_metadef_properties.py +24 -10
  61. openstackclient/tests/unit/network/v2/fakes.py +406 -485
  62. openstackclient/tests/unit/network/v2/test_floating_ip_network.py +13 -19
  63. openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py +2 -2
  64. openstackclient/tests/unit/network/v2/test_ndp_proxy.py +3 -5
  65. openstackclient/tests/unit/network/v2/test_network.py +4 -4
  66. openstackclient/tests/unit/network/v2/test_network_agent.py +15 -29
  67. openstackclient/tests/unit/network/v2/test_network_qos_policy.py +16 -19
  68. openstackclient/tests/unit/network/v2/test_network_qos_rule.py +79 -152
  69. openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py +4 -6
  70. openstackclient/tests/unit/network/v2/test_network_rbac.py +2 -2
  71. openstackclient/tests/unit/network/v2/test_port.py +57 -17
  72. openstackclient/tests/unit/network/v2/test_router.py +73 -57
  73. openstackclient/tests/unit/network/v2/test_security_group_network.py +31 -27
  74. openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +1 -3
  75. openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +82 -39
  76. openstackclient/tests/unit/volume/v2/fakes.py +1 -2
  77. openstackclient/tests/unit/volume/v2/test_service.py +57 -91
  78. openstackclient/tests/unit/volume/v2/test_volume.py +466 -410
  79. openstackclient/tests/unit/volume/v2/test_volume_backup.py +141 -148
  80. openstackclient/tests/unit/volume/v2/test_volume_snapshot.py +293 -283
  81. openstackclient/tests/unit/volume/v3/test_block_storage_log_level.py +61 -71
  82. openstackclient/tests/unit/volume/v3/test_service.py +221 -141
  83. openstackclient/tests/unit/volume/v3/test_volume.py +569 -534
  84. openstackclient/tests/unit/volume/v3/test_volume_attachment.py +1 -1
  85. openstackclient/tests/unit/volume/v3/test_volume_backup.py +198 -203
  86. openstackclient/tests/unit/volume/v3/test_volume_snapshot.py +682 -47
  87. openstackclient/volume/v2/service.py +41 -38
  88. openstackclient/volume/v2/volume.py +140 -88
  89. openstackclient/volume/v2/volume_backup.py +9 -3
  90. openstackclient/volume/v2/volume_snapshot.py +121 -84
  91. openstackclient/volume/v3/block_storage_log_level.py +22 -28
  92. openstackclient/volume/v3/service.py +105 -14
  93. openstackclient/volume/v3/volume.py +287 -99
  94. openstackclient/volume/v3/volume_backup.py +24 -19
  95. openstackclient/volume/v3/volume_group.py +1 -1
  96. openstackclient/volume/v3/volume_snapshot.py +485 -10
  97. {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/AUTHORS +13 -0
  98. python_openstackclient-8.2.0.dist-info/METADATA +264 -0
  99. {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/RECORD +104 -98
  100. {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/entry_points.txt +7 -6
  101. python_openstackclient-8.2.0.dist-info/pbr.json +1 -0
  102. python_openstackclient-8.0.0.dist-info/METADATA +0 -166
  103. python_openstackclient-8.0.0.dist-info/pbr.json +0 -1
  104. {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/LICENSE +0 -0
  105. {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/WHEEL +0 -0
  106. {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
- class CreateVolume(volume_v2.CreateVolume):
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, source_group = self._get_parser(prog_name)
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
- CreateVolume._check_size_arg(parsed_args)
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
- volume_client_sdk = self.app.client_manager.sdk_connection.volume
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.api_version.matches('3.47')
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 = volume_client_sdk.manage_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.property,
385
+ metadata=parsed_args.properties,
236
386
  bootable=parsed_args.bootable,
237
387
  )
238
- return zip(*sorted(volume.items()))
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 = utils.find_resource(
243
- volume_client.volumes, parsed_args.source
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 = utils.find_resource(
251
- volume_client.consistencygroups, parsed_args.consistency_group
252
- ).id
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 = utils.find_resource(
263
- volume_client.volume_snapshots, parsed_args.snapshot
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 = utils.find_resource(
277
- volume_client.backups, parsed_args.backup
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.volumes.create(
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.property,
291
- imageRef=image,
292
- source_volid=source_volume,
293
- consistencygroup_id=consistency_group,
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 or parsed_args.non_bootable:
449
+ if parsed_args.bootable is not None:
299
450
  try:
300
451
  if utils.wait_for_status(
301
- volume_client.volumes.get,
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.volumes.set_bootable(
308
- volume.id, parsed_args.bootable
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
- if parsed_args.read_only or parsed_args.read_write:
468
+
469
+ if parsed_args.read_only is not None:
318
470
  try:
319
471
  if utils.wait_for_status(
320
- volume_client.volumes.get,
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.volumes.update_readonly_flag(
327
- volume.id, parsed_args.read_only
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
- # Remove key links from being displayed
342
- volume._info.update(
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(volume_v2.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 i in parsed_args.volumes:
542
+ for volume in parsed_args.volumes:
379
543
  try:
380
- volume_obj = utils.find_resource(volume_client.volumes, i)
544
+ volume_obj = volume_client.find_volume(
545
+ volume, ignore_missing=False
546
+ )
381
547
  if parsed_args.remote:
382
- volume_client_sdk.unmanage_volume(volume_obj.id)
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.volumes.delete(
387
- volume_obj.id, cascade=parsed_args.purge
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': i, 'e': e},
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 = utils.find_resource(volume_client.volumes, parsed_args.volume)
592
- volume_client.volumes.migrate_volume(
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="store_true",
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="store_true",
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.property:
989
+ if parsed_args.properties:
800
990
  try:
801
991
  volume_client.volumes.set_metadata(
802
- volume.id, parsed_args.property
992
+ volume.id, parsed_args.properties
803
993
  )
804
994
  except Exception as e:
805
- LOG.error(_("Failed to set volume property: %s"), e)
995
+ LOG.error(_("Failed to set volume properties: %s"), e)
806
996
  result += 1
807
- if parsed_args.image_property:
997
+
998
+ if parsed_args.image_properties:
808
999
  try:
809
1000
  volume_client.volumes.set_image_metadata(
810
- volume.id, parsed_args.image_property
1001
+ volume.id, parsed_args.image_properties
811
1002
  )
812
1003
  except Exception as e:
813
- LOG.error(_("Failed to set image property: %s"), e)
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
- if parsed_args.bootable or parsed_args.non_bootable:
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
- if parsed_args.read_only or parsed_args.read_write:
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 = utils.find_resource(volume_client.volumes, parsed_args.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
- # Remove key links from being displayed
938
- volume._info.pop("links", None)
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.property:
1165
+ if parsed_args.properties:
978
1166
  try:
979
1167
  volume_client.volumes.delete_metadata(
980
- volume.id, parsed_args.property
1168
+ volume.id, parsed_args.properties
981
1169
  )
982
1170
  except Exception as e:
983
- LOG.error(_("Failed to unset volume property: %s"), e)
1171
+ LOG.error(_("Failed to unset volume properties: %s"), e)
984
1172
  result += 1
985
1173
 
986
- if parsed_args.image_property:
1174
+ if parsed_args.image_properties:
987
1175
  try:
988
1176
  volume_client.volumes.delete_image_metadata(
989
- volume.id, parsed_args.image_property
1177
+ volume.id, parsed_args.image_properties
990
1178
  )
991
1179
  except Exception as e:
992
- LOG.error(_("Failed to unset image property: %s"), e)
1180
+ LOG.error(_("Failed to unset image properties: %s"), e)
993
1181
  result += 1
994
1182
 
995
1183
  if result > 0: