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,7 +18,6 @@ import copy
18
18
  import functools
19
19
  import logging
20
20
 
21
- from cinderclient import api_versions
22
21
  from cliff import columns as cliff_columns
23
22
  from openstack import utils as sdk_utils
24
23
  from osc_lib.cli import parseractions
@@ -512,13 +511,19 @@ class SetVolumeBackup(command.Command):
512
511
  return parser
513
512
 
514
513
  def take_action(self, parsed_args):
515
- volume_client = self.app.client_manager.volume
516
- backup = utils.find_resource(volume_client.backups, parsed_args.backup)
514
+ volume_client = self.app.client_manager.sdk_connection.volume
515
+
516
+ backup = volume_client.find_backup(
517
+ parsed_args.backup,
518
+ ignore_missing=False,
519
+ )
517
520
 
518
521
  result = 0
519
522
  if parsed_args.state:
520
523
  try:
521
- volume_client.backups.reset_state(backup.id, parsed_args.state)
524
+ volume_client.reset_backup_status(
525
+ backup, status=parsed_args.state
526
+ )
522
527
  except Exception as e:
523
528
  LOG.error(_("Failed to set backup state: %s"), e)
524
529
  result += 1
@@ -526,7 +531,7 @@ class SetVolumeBackup(command.Command):
526
531
  kwargs = {}
527
532
 
528
533
  if parsed_args.name:
529
- if volume_client.api_version < api_versions.APIVersion('3.9'):
534
+ if not sdk_utils.supports_microversion(volume_client, '3.9'):
530
535
  msg = _(
531
536
  '--os-volume-api-version 3.9 or greater is required to '
532
537
  'support the --name option'
@@ -536,7 +541,7 @@ class SetVolumeBackup(command.Command):
536
541
  kwargs['name'] = parsed_args.name
537
542
 
538
543
  if parsed_args.description:
539
- if volume_client.api_version < api_versions.APIVersion('3.9'):
544
+ if not sdk_utils.supports_microversion(volume_client, '3.9'):
540
545
  msg = _(
541
546
  '--os-volume-api-version 3.9 or greater is required to '
542
547
  'support the --description option'
@@ -546,7 +551,7 @@ class SetVolumeBackup(command.Command):
546
551
  kwargs['description'] = parsed_args.description
547
552
 
548
553
  if parsed_args.no_property:
549
- if volume_client.api_version < api_versions.APIVersion('3.43'):
554
+ if not sdk_utils.supports_microversion(volume_client, '3.43'):
550
555
  msg = _(
551
556
  '--os-volume-api-version 3.43 or greater is required to '
552
557
  'support the --no-property option'
@@ -554,14 +559,14 @@ class SetVolumeBackup(command.Command):
554
559
  raise exceptions.CommandError(msg)
555
560
 
556
561
  if parsed_args.properties:
557
- if volume_client.api_version < api_versions.APIVersion('3.43'):
562
+ if not sdk_utils.supports_microversion(volume_client, '3.43'):
558
563
  msg = _(
559
564
  '--os-volume-api-version 3.43 or greater is required to '
560
565
  'support the --property option'
561
566
  )
562
567
  raise exceptions.CommandError(msg)
563
568
 
564
- if volume_client.api_version >= api_versions.APIVersion('3.43'):
569
+ if sdk_utils.supports_microversion(volume_client, '3.43'):
565
570
  metadata = copy.deepcopy(backup.metadata)
566
571
 
567
572
  if parsed_args.no_property:
@@ -572,7 +577,7 @@ class SetVolumeBackup(command.Command):
572
577
 
573
578
  if kwargs:
574
579
  try:
575
- volume_client.backups.update(backup.id, **kwargs)
580
+ volume_client.update_backup(backup, **kwargs)
576
581
  except Exception as e:
577
582
  LOG.error("Failed to update backup: %s", e)
578
583
  result += 1
@@ -608,16 +613,18 @@ class UnsetVolumeBackup(command.Command):
608
613
  return parser
609
614
 
610
615
  def take_action(self, parsed_args):
611
- volume_client = self.app.client_manager.volume
616
+ volume_client = self.app.client_manager.sdk_connection.volume
612
617
 
613
- if volume_client.api_version < api_versions.APIVersion('3.43'):
618
+ if not sdk_utils.supports_microversion(volume_client, '3.43'):
614
619
  msg = _(
615
620
  '--os-volume-api-version 3.43 or greater is required to '
616
621
  'support the --property option'
617
622
  )
618
623
  raise exceptions.CommandError(msg)
619
624
 
620
- backup = utils.find_resource(volume_client.backups, parsed_args.backup)
625
+ backup = volume_client.find_backup(
626
+ parsed_args.backup, ignore_missing=False
627
+ )
621
628
  metadata = copy.deepcopy(backup.metadata)
622
629
 
623
630
  for key in parsed_args.properties:
@@ -632,11 +639,7 @@ class UnsetVolumeBackup(command.Command):
632
639
 
633
640
  del metadata[key]
634
641
 
635
- kwargs = {
636
- 'metadata': metadata,
637
- }
638
-
639
- volume_client.backups.update(backup.id, **kwargs)
642
+ volume_client.delete_backup_metadata(backup, keys=list(metadata))
640
643
 
641
644
 
642
645
  class ShowVolumeBackup(command.ShowOne):
@@ -653,7 +656,9 @@ class ShowVolumeBackup(command.ShowOne):
653
656
 
654
657
  def take_action(self, parsed_args):
655
658
  volume_client = self.app.client_manager.sdk_connection.volume
656
- backup = volume_client.find_backup(parsed_args.backup)
659
+ backup = volume_client.find_backup(
660
+ parsed_args.backup, ignore_missing=False
661
+ )
657
662
  columns: tuple[str, ...] = (
658
663
  "availability_zone",
659
664
  "container",
@@ -552,7 +552,7 @@ class ShowVolumeGroup(command.ShowOne):
552
552
  parsed_args.group,
553
553
  )
554
554
 
555
- group = volume_client.groups.show(group.id, **kwargs)
555
+ group = volume_client.groups.get(group.id, **kwargs)
556
556
 
557
557
  if parsed_args.show_replication_targets:
558
558
  replication_targets = (
@@ -14,17 +14,180 @@
14
14
 
15
15
  """Volume v3 snapshot action implementations"""
16
16
 
17
+ import functools
17
18
  import logging
19
+ import typing as ty
18
20
 
21
+ from cliff import columns as cliff_columns
22
+ from openstack.block_storage.v3 import snapshot as _snapshot
23
+ from osc_lib.cli import format_columns
24
+ from osc_lib.cli import parseractions
19
25
  from osc_lib.command import command
20
26
  from osc_lib import exceptions
21
27
  from osc_lib import utils
22
28
 
29
+ from openstackclient.common import pagination
23
30
  from openstackclient.i18n import _
31
+ from openstackclient.identity import common as identity_common
24
32
 
25
33
  LOG = logging.getLogger(__name__)
26
34
 
27
35
 
36
+ class VolumeIdColumn(cliff_columns.FormattableColumn):
37
+ """Formattable column for volume ID column.
38
+
39
+ Unlike the parent FormattableColumn class, the initializer of the
40
+ class takes volume_cache as the second argument.
41
+ osc_lib.utils.get_item_properties instantiate cliff FormattableColumn
42
+ object with a single parameter "column value", so you need to pass
43
+ a partially initialized class like
44
+ ``functools.partial(VolumeIdColumn, volume_cache)``.
45
+ """
46
+
47
+ def __init__(self, value, volume_cache=None):
48
+ super().__init__(value)
49
+ self._volume_cache = volume_cache or {}
50
+
51
+ def human_readable(self):
52
+ """Return a volume name if available
53
+
54
+ :rtype: either the volume ID or name
55
+ """
56
+ volume_id = self._value
57
+ volume = volume_id
58
+ if volume_id in self._volume_cache.keys():
59
+ volume = self._volume_cache[volume_id].name
60
+ return volume
61
+
62
+
63
+ def _format_snapshot(snapshot: _snapshot.Snapshot) -> dict[str, ty.Any]:
64
+ # Some columns returned by openstacksdk should not be shown because they're
65
+ # either irrelevant or duplicates
66
+ ignored_columns = {
67
+ # computed columns
68
+ 'location',
69
+ # create-only columns
70
+ 'consumes_quota',
71
+ 'force',
72
+ 'group_snapshot_id',
73
+ # ignored columns
74
+ 'os-extended-snapshot-attributes:progress',
75
+ 'os-extended-snapshot-attributes:project_id',
76
+ 'updated_at',
77
+ 'user_id',
78
+ # unnecessary columns
79
+ 'links',
80
+ }
81
+
82
+ info = snapshot.to_dict(original_names=True)
83
+ data = {}
84
+ for key, value in info.items():
85
+ if key in ignored_columns:
86
+ continue
87
+
88
+ data[key] = value
89
+
90
+ data.update(
91
+ {
92
+ 'properties': format_columns.DictColumn(data.pop('metadata')),
93
+ }
94
+ )
95
+
96
+ return data
97
+
98
+
99
+ class CreateVolumeSnapshot(command.ShowOne):
100
+ _description = _("Create new volume snapshot")
101
+
102
+ def get_parser(self, prog_name):
103
+ parser = super().get_parser(prog_name)
104
+ parser.add_argument(
105
+ "snapshot_name",
106
+ metavar="<snapshot-name>",
107
+ help=_("Name of the new snapshot"),
108
+ )
109
+ parser.add_argument(
110
+ "--volume",
111
+ metavar="<volume>",
112
+ help=_(
113
+ "Volume to snapshot (name or ID) (default is <snapshot-name>)"
114
+ ),
115
+ )
116
+ parser.add_argument(
117
+ "--description",
118
+ metavar="<description>",
119
+ help=_("Description of the snapshot"),
120
+ )
121
+ parser.add_argument(
122
+ "--force",
123
+ action="store_true",
124
+ default=False,
125
+ help=_(
126
+ "Create a snapshot attached to an instance. Default is False"
127
+ ),
128
+ )
129
+ parser.add_argument(
130
+ "--property",
131
+ metavar="<key=value>",
132
+ dest='properties',
133
+ action=parseractions.KeyValueAction,
134
+ help=_(
135
+ "Set a property to this snapshot "
136
+ "(repeat option to set multiple properties)"
137
+ ),
138
+ )
139
+ parser.add_argument(
140
+ "--remote-source",
141
+ metavar="<key=value>",
142
+ action=parseractions.KeyValueAction,
143
+ help=_(
144
+ "The attribute(s) of the existing remote volume snapshot "
145
+ "(admin required) (repeat option to specify multiple "
146
+ "attributes) e.g.: '--remote-source source-name=test_name "
147
+ "--remote-source source-id=test_id'"
148
+ ),
149
+ )
150
+ return parser
151
+
152
+ def take_action(self, parsed_args):
153
+ volume_client = self.app.client_manager.sdk_connection.volume
154
+
155
+ volume = parsed_args.volume
156
+ if not parsed_args.volume:
157
+ volume = parsed_args.snapshot_name
158
+ volume_id = volume_client.find_volume(volume, ignore_missing=False).id
159
+
160
+ if parsed_args.remote_source:
161
+ # Create a new snapshot from an existing remote snapshot source
162
+ if parsed_args.force:
163
+ msg = _(
164
+ "'--force' option will not work when you create "
165
+ "new volume snapshot from an existing remote "
166
+ "volume snapshot"
167
+ )
168
+ LOG.warning(msg)
169
+
170
+ snapshot = volume_client.manage_snapshot(
171
+ volume_id=volume_id,
172
+ ref=parsed_args.remote_source,
173
+ name=parsed_args.snapshot_name,
174
+ description=parsed_args.description,
175
+ metadata=parsed_args.properties,
176
+ )
177
+ else:
178
+ # Create a new snapshot from scratch
179
+ snapshot = volume_client.create_snapshot(
180
+ volume_id=volume_id,
181
+ force=parsed_args.force,
182
+ name=parsed_args.snapshot_name,
183
+ description=parsed_args.description,
184
+ metadata=parsed_args.properties,
185
+ )
186
+
187
+ data = _format_snapshot(snapshot)
188
+ return zip(*sorted(data.items()))
189
+
190
+
28
191
  class DeleteVolumeSnapshot(command.Command):
29
192
  _description = _("Delete volume snapshot(s)")
30
193
 
@@ -55,9 +218,7 @@ class DeleteVolumeSnapshot(command.Command):
55
218
  return parser
56
219
 
57
220
  def take_action(self, parsed_args):
58
- volume_client = self.app.client_manager.volume
59
- volume_client_sdk = self.app.client_manager.sdk_connection.volume
60
-
221
+ volume_client = self.app.client_manager.sdk_connection.volume
61
222
  result = 0
62
223
 
63
224
  if parsed_args.remote:
@@ -68,16 +229,16 @@ class DeleteVolumeSnapshot(command.Command):
68
229
  )
69
230
  raise exceptions.CommandError(msg)
70
231
 
71
- for i in parsed_args.snapshots:
232
+ for snapshot in parsed_args.snapshots:
72
233
  try:
73
- snapshot_id = utils.find_resource(
74
- volume_client.volume_snapshots, i
234
+ snapshot_id = volume_client.find_snapshot(
235
+ snapshot, ignore_missing=False
75
236
  ).id
76
237
  if parsed_args.remote:
77
- volume_client_sdk.unmanage_snapshot(snapshot_id)
238
+ volume_client.unmanage_snapshot(snapshot_id)
78
239
  else:
79
- volume_client.volume_snapshots.delete(
80
- snapshot_id, parsed_args.force
240
+ volume_client.delete_snapshot(
241
+ snapshot_id, force=parsed_args.force
81
242
  )
82
243
  except Exception as e:
83
244
  result += 1
@@ -86,7 +247,7 @@ class DeleteVolumeSnapshot(command.Command):
86
247
  "Failed to delete snapshot with "
87
248
  "name or ID '%(snapshot)s': %(e)s"
88
249
  )
89
- % {'snapshot': i, 'e': e}
250
+ % {'snapshot': snapshot, 'e': e}
90
251
  )
91
252
 
92
253
  if result > 0:
@@ -96,3 +257,317 @@ class DeleteVolumeSnapshot(command.Command):
96
257
  'total': total,
97
258
  }
98
259
  raise exceptions.CommandError(msg)
260
+
261
+
262
+ class ListVolumeSnapshot(command.Lister):
263
+ _description = _("List volume snapshots")
264
+
265
+ def get_parser(self, prog_name):
266
+ parser = super().get_parser(prog_name)
267
+ parser.add_argument(
268
+ '--all-projects',
269
+ action='store_true',
270
+ default=False,
271
+ help=_('Include all projects (admin only)'),
272
+ )
273
+ parser.add_argument(
274
+ '--project',
275
+ metavar='<project>',
276
+ help=_('Filter results by project (name or ID) (admin only)'),
277
+ )
278
+ identity_common.add_project_domain_option_to_parser(parser)
279
+ parser.add_argument(
280
+ '--long',
281
+ action='store_true',
282
+ default=False,
283
+ help=_('List additional fields in output'),
284
+ )
285
+ parser.add_argument(
286
+ '--name',
287
+ metavar='<name>',
288
+ default=None,
289
+ help=_('Filters results by a name.'),
290
+ )
291
+ parser.add_argument(
292
+ '--status',
293
+ metavar='<status>',
294
+ choices=[
295
+ 'available',
296
+ 'error',
297
+ 'creating',
298
+ 'deleting',
299
+ 'error_deleting',
300
+ ],
301
+ help=_(
302
+ "Filters results by a status. "
303
+ "('available', 'error', 'creating', 'deleting'"
304
+ " or 'error_deleting')"
305
+ ),
306
+ )
307
+ parser.add_argument(
308
+ '--volume',
309
+ metavar='<volume>',
310
+ default=None,
311
+ help=_('Filters results by a volume (name or ID).'),
312
+ )
313
+ pagination.add_marker_pagination_option_to_parser(parser)
314
+ return parser
315
+
316
+ def take_action(self, parsed_args):
317
+ volume_client = self.app.client_manager.sdk_connection.volume
318
+ identity_client = self.app.client_manager.identity
319
+
320
+ columns: tuple[str, ...] = (
321
+ 'id',
322
+ 'name',
323
+ 'description',
324
+ 'status',
325
+ 'size',
326
+ )
327
+ column_headers: tuple[str, ...] = (
328
+ 'ID',
329
+ 'Name',
330
+ 'Description',
331
+ 'Status',
332
+ 'Size',
333
+ )
334
+ if parsed_args.long:
335
+ columns += (
336
+ 'created_at',
337
+ 'volume_id',
338
+ 'metadata',
339
+ )
340
+ column_headers += (
341
+ 'Created At',
342
+ 'Volume',
343
+ 'Properties',
344
+ )
345
+
346
+ # Cache the volume list
347
+ volume_cache = {}
348
+ try:
349
+ for s in volume_client.volumes():
350
+ volume_cache[s.id] = s
351
+ except Exception: # noqa: S110
352
+ # Just forget it if there's any trouble
353
+ pass
354
+ _VolumeIdColumn = functools.partial(
355
+ VolumeIdColumn, volume_cache=volume_cache
356
+ )
357
+
358
+ volume_id = None
359
+ if parsed_args.volume:
360
+ volume_id = volume_client.find_volume(
361
+ parsed_args.volume, ignore_missing=False
362
+ ).id
363
+
364
+ project_id = None
365
+ if parsed_args.project:
366
+ project_id = identity_common.find_project(
367
+ identity_client,
368
+ parsed_args.project,
369
+ parsed_args.project_domain,
370
+ ).id
371
+
372
+ # set value of 'all_tenants' when using project option
373
+ all_projects = (
374
+ True if parsed_args.project else parsed_args.all_projects
375
+ )
376
+
377
+ data = volume_client.snapshots(
378
+ marker=parsed_args.marker,
379
+ limit=parsed_args.limit,
380
+ all_projects=all_projects,
381
+ project_id=project_id,
382
+ name=parsed_args.name,
383
+ status=parsed_args.status,
384
+ volume_id=volume_id,
385
+ )
386
+ return (
387
+ column_headers,
388
+ (
389
+ utils.get_item_properties(
390
+ s,
391
+ columns,
392
+ formatters={
393
+ 'metadata': format_columns.DictColumn,
394
+ 'volume_id': _VolumeIdColumn,
395
+ },
396
+ )
397
+ for s in data
398
+ ),
399
+ )
400
+
401
+
402
+ class SetVolumeSnapshot(command.Command):
403
+ _description = _("Set volume snapshot properties")
404
+
405
+ def get_parser(self, prog_name):
406
+ parser = super().get_parser(prog_name)
407
+ parser.add_argument(
408
+ 'snapshot',
409
+ metavar='<snapshot>',
410
+ help=_('Snapshot to modify (name or ID)'),
411
+ )
412
+ parser.add_argument(
413
+ '--name', metavar='<name>', help=_('New snapshot name')
414
+ )
415
+ parser.add_argument(
416
+ '--description',
417
+ metavar='<description>',
418
+ help=_('New snapshot description'),
419
+ )
420
+ parser.add_argument(
421
+ "--no-property",
422
+ dest="no_property",
423
+ action="store_true",
424
+ help=_(
425
+ "Remove all properties from <snapshot> "
426
+ "(specify both --no-property and --property to "
427
+ "remove the current properties before setting "
428
+ "new properties.)"
429
+ ),
430
+ )
431
+ parser.add_argument(
432
+ '--property',
433
+ metavar='<key=value>',
434
+ action=parseractions.KeyValueAction,
435
+ dest='properties',
436
+ help=_(
437
+ 'Property to add/change for this snapshot '
438
+ '(repeat option to set multiple properties)'
439
+ ),
440
+ )
441
+ parser.add_argument(
442
+ '--state',
443
+ metavar='<state>',
444
+ choices=[
445
+ 'available',
446
+ 'error',
447
+ 'creating',
448
+ 'deleting',
449
+ 'error_deleting',
450
+ ],
451
+ help=_(
452
+ 'New snapshot state. ("available", "error", "creating", '
453
+ '"deleting", or "error_deleting") (admin only) '
454
+ '(This option simply changes the state of the snapshot '
455
+ 'in the database with no regard to actual status, '
456
+ 'exercise caution when using)'
457
+ ),
458
+ )
459
+ return parser
460
+
461
+ def take_action(self, parsed_args):
462
+ volume_client = self.app.client_manager.sdk_connection.volume
463
+
464
+ snapshot = volume_client.find_snapshot(
465
+ parsed_args.snapshot, ignore_missing=False
466
+ )
467
+
468
+ result = 0
469
+ if parsed_args.no_property:
470
+ try:
471
+ volume_client.delete_snapshot_metadata(
472
+ snapshot.id, keys=list(snapshot.metadata)
473
+ )
474
+ except Exception as e:
475
+ LOG.error(_("Failed to clean snapshot properties: %s"), e)
476
+ result += 1
477
+
478
+ if parsed_args.properties:
479
+ try:
480
+ volume_client.set_snapshot_metadata(
481
+ snapshot.id, **parsed_args.properties
482
+ )
483
+ except Exception as e:
484
+ LOG.error(_("Failed to set snapshot property: %s"), e)
485
+ result += 1
486
+
487
+ if parsed_args.state:
488
+ try:
489
+ volume_client.reset_snapshot_status(
490
+ snapshot.id, parsed_args.state
491
+ )
492
+ except Exception as e:
493
+ LOG.error(_("Failed to set snapshot state: %s"), e)
494
+ result += 1
495
+
496
+ kwargs = {}
497
+ if parsed_args.name:
498
+ kwargs['name'] = parsed_args.name
499
+ if parsed_args.description:
500
+ kwargs['description'] = parsed_args.description
501
+ if kwargs:
502
+ try:
503
+ volume_client.update_snapshot(snapshot.id, **kwargs)
504
+ except Exception as e:
505
+ LOG.error(
506
+ _("Failed to update snapshot name or description: %s"),
507
+ e,
508
+ )
509
+ result += 1
510
+
511
+ if result > 0:
512
+ raise exceptions.CommandError(
513
+ _("One or more of the set operations failed")
514
+ )
515
+
516
+
517
+ class ShowVolumeSnapshot(command.ShowOne):
518
+ _description = _("Display volume snapshot details")
519
+
520
+ def get_parser(self, prog_name):
521
+ parser = super().get_parser(prog_name)
522
+ parser.add_argument(
523
+ "snapshot",
524
+ metavar="<snapshot>",
525
+ help=_("Snapshot to display (name or ID)"),
526
+ )
527
+ return parser
528
+
529
+ def take_action(self, parsed_args):
530
+ volume_client = self.app.client_manager.sdk_connection.volume
531
+
532
+ snapshot = volume_client.find_snapshot(
533
+ parsed_args.snapshot, ignore_missing=False
534
+ )
535
+
536
+ data = _format_snapshot(snapshot)
537
+ return zip(*sorted(data.items()))
538
+
539
+
540
+ class UnsetVolumeSnapshot(command.Command):
541
+ _description = _("Unset volume snapshot properties")
542
+
543
+ def get_parser(self, prog_name):
544
+ parser = super().get_parser(prog_name)
545
+ parser.add_argument(
546
+ 'snapshot',
547
+ metavar='<snapshot>',
548
+ help=_('Snapshot to modify (name or ID)'),
549
+ )
550
+ parser.add_argument(
551
+ '--property',
552
+ metavar='<key>',
553
+ dest='properties',
554
+ action='append',
555
+ default=[],
556
+ help=_(
557
+ 'Property to remove from snapshot '
558
+ '(repeat option to remove multiple properties)'
559
+ ),
560
+ )
561
+ return parser
562
+
563
+ def take_action(self, parsed_args):
564
+ volume_client = self.app.client_manager.sdk_connection.volume
565
+
566
+ snapshot = volume_client.find_snapshot(
567
+ parsed_args.snapshot, ignore_missing=False
568
+ )
569
+
570
+ if parsed_args.properties:
571
+ volume_client.delete_snapshot_metadata(
572
+ snapshot.id, keys=parsed_args.properties
573
+ )