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
@@ -14,11 +14,12 @@
14
14
 
15
15
  """Volume v2 snapshot action implementations"""
16
16
 
17
- import copy
18
17
  import functools
19
18
  import logging
19
+ import typing as ty
20
20
 
21
21
  from cliff import columns as cliff_columns
22
+ from openstack.block_storage.v2 import snapshot as _snapshot
22
23
  from osc_lib.cli import format_columns
23
24
  from osc_lib.cli import parseractions
24
25
  from osc_lib.command import command
@@ -60,6 +61,42 @@ class VolumeIdColumn(cliff_columns.FormattableColumn):
60
61
  return volume
61
62
 
62
63
 
64
+ def _format_snapshot(snapshot: _snapshot.Snapshot) -> dict[str, ty.Any]:
65
+ # Some columns returned by openstacksdk should not be shown because they're
66
+ # either irrelevant or duplicates
67
+ ignored_columns = {
68
+ # computed columns
69
+ 'location',
70
+ # create-only columns
71
+ 'consumes_quota',
72
+ 'force',
73
+ 'group_snapshot_id',
74
+ # ignored columns
75
+ 'os-extended-snapshot-attributes:progress',
76
+ 'os-extended-snapshot-attributes:project_id',
77
+ 'updated_at',
78
+ 'user_id',
79
+ # unnecessary columns
80
+ 'links',
81
+ }
82
+
83
+ info = snapshot.to_dict(original_names=True)
84
+ data = {}
85
+ for key, value in info.items():
86
+ if key in ignored_columns:
87
+ continue
88
+
89
+ data[key] = value
90
+
91
+ data.update(
92
+ {
93
+ 'properties': format_columns.DictColumn(data.pop('metadata')),
94
+ }
95
+ )
96
+
97
+ return data
98
+
99
+
63
100
  class CreateVolumeSnapshot(command.ShowOne):
64
101
  _description = _("Create new volume snapshot")
65
102
 
@@ -94,6 +131,7 @@ class CreateVolumeSnapshot(command.ShowOne):
94
131
  "--property",
95
132
  metavar="<key=value>",
96
133
  action=parseractions.KeyValueAction,
134
+ dest="properties",
97
135
  help=_(
98
136
  "Set a property to this snapshot "
99
137
  "(repeat option to set multiple properties)"
@@ -113,11 +151,13 @@ class CreateVolumeSnapshot(command.ShowOne):
113
151
  return parser
114
152
 
115
153
  def take_action(self, parsed_args):
116
- volume_client = self.app.client_manager.volume
154
+ volume_client = self.app.client_manager.sdk_connection.volume
155
+
117
156
  volume = parsed_args.volume
118
157
  if not parsed_args.volume:
119
158
  volume = parsed_args.snapshot_name
120
- volume_id = utils.find_resource(volume_client.volumes, volume).id
159
+ volume_id = volume_client.find_volume(volume, ignore_missing=False).id
160
+
121
161
  if parsed_args.remote_source:
122
162
  # Create a new snapshot from an existing remote snapshot source
123
163
  if parsed_args.force:
@@ -127,30 +167,26 @@ class CreateVolumeSnapshot(command.ShowOne):
127
167
  "volume snapshot"
128
168
  )
129
169
  LOG.warning(msg)
130
- snapshot = volume_client.volume_snapshots.manage(
170
+
171
+ snapshot = volume_client.manage_snapshot(
131
172
  volume_id=volume_id,
132
173
  ref=parsed_args.remote_source,
133
174
  name=parsed_args.snapshot_name,
134
175
  description=parsed_args.description,
135
- metadata=parsed_args.property,
176
+ metadata=parsed_args.properties,
136
177
  )
137
178
  else:
138
179
  # create a new snapshot from scratch
139
- snapshot = volume_client.volume_snapshots.create(
140
- volume_id,
180
+ snapshot = volume_client.create_snapshot(
181
+ volume_id=volume_id,
141
182
  force=parsed_args.force,
142
183
  name=parsed_args.snapshot_name,
143
184
  description=parsed_args.description,
144
- metadata=parsed_args.property,
185
+ metadata=parsed_args.properties,
145
186
  )
146
- snapshot._info.update(
147
- {
148
- 'properties': format_columns.DictColumn(
149
- snapshot._info.pop('metadata')
150
- )
151
- }
152
- )
153
- return zip(*sorted(snapshot._info.items()))
187
+
188
+ data = _format_snapshot(snapshot)
189
+ return zip(*sorted(data.items()))
154
190
 
155
191
 
156
192
  class DeleteVolumeSnapshot(command.Command):
@@ -175,16 +211,16 @@ class DeleteVolumeSnapshot(command.Command):
175
211
  return parser
176
212
 
177
213
  def take_action(self, parsed_args):
178
- volume_client = self.app.client_manager.volume
214
+ volume_client = self.app.client_manager.sdk_connection.volume
179
215
  result = 0
180
216
 
181
- for i in parsed_args.snapshots:
217
+ for snapshot in parsed_args.snapshots:
182
218
  try:
183
- snapshot_id = utils.find_resource(
184
- volume_client.volume_snapshots, i
219
+ snapshot_id = volume_client.find_snapshot(
220
+ snapshot, ignore_missing=False
185
221
  ).id
186
- volume_client.volume_snapshots.delete(
187
- snapshot_id, parsed_args.force
222
+ volume_client.delete_snapshot(
223
+ snapshot_id, force=parsed_args.force
188
224
  )
189
225
  except Exception as e:
190
226
  result += 1
@@ -193,7 +229,7 @@ class DeleteVolumeSnapshot(command.Command):
193
229
  "Failed to delete snapshot with "
194
230
  "name or ID '%(snapshot)s': %(e)s"
195
231
  )
196
- % {'snapshot': i, 'e': e}
232
+ % {'snapshot': snapshot, 'e': e}
197
233
  )
198
234
 
199
235
  if result > 0:
@@ -260,31 +296,39 @@ class ListVolumeSnapshot(command.Lister):
260
296
  return parser
261
297
 
262
298
  def take_action(self, parsed_args):
263
- volume_client = self.app.client_manager.volume
299
+ volume_client = self.app.client_manager.sdk_connection.volume
264
300
  identity_client = self.app.client_manager.identity
265
301
 
302
+ columns: tuple[str, ...] = (
303
+ 'id',
304
+ 'name',
305
+ 'description',
306
+ 'status',
307
+ 'size',
308
+ )
309
+ column_headers: tuple[str, ...] = (
310
+ 'ID',
311
+ 'Name',
312
+ 'Description',
313
+ 'Status',
314
+ 'Size',
315
+ )
266
316
  if parsed_args.long:
267
- columns = [
268
- 'ID',
269
- 'Name',
270
- 'Description',
271
- 'Status',
272
- 'Size',
317
+ columns += (
318
+ 'created_at',
319
+ 'volume_id',
320
+ 'metadata',
321
+ )
322
+ column_headers += (
273
323
  'Created At',
274
- 'Volume ID',
275
- 'Metadata',
276
- ]
277
- column_headers = copy.deepcopy(columns)
278
- column_headers[6] = 'Volume'
279
- column_headers[7] = 'Properties'
280
- else:
281
- columns = ['ID', 'Name', 'Description', 'Status', 'Size']
282
- column_headers = copy.deepcopy(columns)
324
+ 'Volume',
325
+ 'Properties',
326
+ )
283
327
 
284
328
  # Cache the volume list
285
329
  volume_cache = {}
286
330
  try:
287
- for s in volume_client.volumes.list():
331
+ for s in volume_client.volumes():
288
332
  volume_cache[s.id] = s
289
333
  except Exception: # noqa: S110
290
334
  # Just forget it if there's any trouble
@@ -295,8 +339,8 @@ class ListVolumeSnapshot(command.Lister):
295
339
 
296
340
  volume_id = None
297
341
  if parsed_args.volume:
298
- volume_id = utils.find_resource(
299
- volume_client.volumes, parsed_args.volume
342
+ volume_id = volume_client.find_volume(
343
+ parsed_args.volume, ignore_missing=False
300
344
  ).id
301
345
 
302
346
  project_id = None
@@ -312,18 +356,14 @@ class ListVolumeSnapshot(command.Lister):
312
356
  True if parsed_args.project else parsed_args.all_projects
313
357
  )
314
358
 
315
- search_opts = {
316
- 'all_tenants': all_projects,
317
- 'project_id': project_id,
318
- 'name': parsed_args.name,
319
- 'status': parsed_args.status,
320
- 'volume_id': volume_id,
321
- }
322
-
323
- data = volume_client.volume_snapshots.list(
324
- search_opts=search_opts,
359
+ data = volume_client.snapshots(
325
360
  marker=parsed_args.marker,
326
361
  limit=parsed_args.limit,
362
+ all_projects=all_projects,
363
+ project_id=project_id,
364
+ name=parsed_args.name,
365
+ status=parsed_args.status,
366
+ volume_id=volume_id,
327
367
  )
328
368
  return (
329
369
  column_headers,
@@ -332,8 +372,8 @@ class ListVolumeSnapshot(command.Lister):
332
372
  s,
333
373
  columns,
334
374
  formatters={
335
- 'Metadata': format_columns.DictColumn,
336
- 'Volume ID': _VolumeIdColumn,
375
+ 'metadata': format_columns.DictColumn,
376
+ 'volume_id': _VolumeIdColumn,
337
377
  },
338
378
  )
339
379
  for s in data
@@ -374,6 +414,7 @@ class SetVolumeSnapshot(command.Command):
374
414
  '--property',
375
415
  metavar='<key=value>',
376
416
  action=parseractions.KeyValueAction,
417
+ dest='properties',
377
418
  help=_(
378
419
  'Property to add/change for this snapshot '
379
420
  '(repeat option to set multiple properties)'
@@ -400,27 +441,26 @@ class SetVolumeSnapshot(command.Command):
400
441
  return parser
401
442
 
402
443
  def take_action(self, parsed_args):
403
- volume_client = self.app.client_manager.volume
404
- snapshot = utils.find_resource(
405
- volume_client.volume_snapshots, parsed_args.snapshot
444
+ volume_client = self.app.client_manager.sdk_connection.volume
445
+
446
+ snapshot = volume_client.find_snapshot(
447
+ parsed_args.snapshot, ignore_missing=False
406
448
  )
407
449
 
408
450
  result = 0
409
451
  if parsed_args.no_property:
410
452
  try:
411
- key_list = snapshot.metadata.keys()
412
- volume_client.volume_snapshots.delete_metadata(
413
- snapshot.id,
414
- list(key_list),
453
+ volume_client.delete_snapshot_metadata(
454
+ snapshot.id, keys=list(snapshot.metadata)
415
455
  )
416
456
  except Exception as e:
417
457
  LOG.error(_("Failed to clean snapshot properties: %s"), e)
418
458
  result += 1
419
459
 
420
- if parsed_args.property:
460
+ if parsed_args.properties:
421
461
  try:
422
- volume_client.volume_snapshots.set_metadata(
423
- snapshot.id, parsed_args.property
462
+ volume_client.set_snapshot_metadata(
463
+ snapshot.id, **parsed_args.properties
424
464
  )
425
465
  except Exception as e:
426
466
  LOG.error(_("Failed to set snapshot property: %s"), e)
@@ -428,7 +468,7 @@ class SetVolumeSnapshot(command.Command):
428
468
 
429
469
  if parsed_args.state:
430
470
  try:
431
- volume_client.volume_snapshots.reset_state(
471
+ volume_client.reset_snapshot_status(
432
472
  snapshot.id, parsed_args.state
433
473
  )
434
474
  except Exception as e:
@@ -442,7 +482,7 @@ class SetVolumeSnapshot(command.Command):
442
482
  kwargs['description'] = parsed_args.description
443
483
  if kwargs:
444
484
  try:
445
- volume_client.volume_snapshots.update(snapshot.id, **kwargs)
485
+ volume_client.update_snapshot(snapshot.id, **kwargs)
446
486
  except Exception as e:
447
487
  LOG.error(
448
488
  _("Failed to update snapshot name or description: %s"),
@@ -469,18 +509,14 @@ class ShowVolumeSnapshot(command.ShowOne):
469
509
  return parser
470
510
 
471
511
  def take_action(self, parsed_args):
472
- volume_client = self.app.client_manager.volume
473
- snapshot = utils.find_resource(
474
- volume_client.volume_snapshots, parsed_args.snapshot
475
- )
476
- snapshot._info.update(
477
- {
478
- 'properties': format_columns.DictColumn(
479
- snapshot._info.pop('metadata')
480
- )
481
- }
512
+ volume_client = self.app.client_manager.sdk_connection.volume
513
+
514
+ snapshot = volume_client.find_snapshot(
515
+ parsed_args.snapshot, ignore_missing=False
482
516
  )
483
- return zip(*sorted(snapshot._info.items()))
517
+
518
+ data = _format_snapshot(snapshot)
519
+ return zip(*sorted(data.items()))
484
520
 
485
521
 
486
522
  class UnsetVolumeSnapshot(command.Command):
@@ -498,6 +534,7 @@ class UnsetVolumeSnapshot(command.Command):
498
534
  metavar='<key>',
499
535
  action='append',
500
536
  default=[],
537
+ dest='properties',
501
538
  help=_(
502
539
  'Property to remove from snapshot '
503
540
  '(repeat option to remove multiple properties)'
@@ -506,13 +543,13 @@ class UnsetVolumeSnapshot(command.Command):
506
543
  return parser
507
544
 
508
545
  def take_action(self, parsed_args):
509
- volume_client = self.app.client_manager.volume
510
- snapshot = utils.find_resource(
511
- volume_client.volume_snapshots, parsed_args.snapshot
546
+ volume_client = self.app.client_manager.sdk_connection.volume
547
+
548
+ snapshot = volume_client.find_snapshot(
549
+ parsed_args.snapshot, ignore_missing=False
512
550
  )
513
551
 
514
- if parsed_args.property:
515
- volume_client.volume_snapshots.delete_metadata(
516
- snapshot.id,
517
- parsed_args.property,
552
+ if parsed_args.properties:
553
+ volume_client.delete_snapshot_metadata(
554
+ snapshot.id, keys=parsed_args.properties
518
555
  )
@@ -14,10 +14,9 @@
14
14
 
15
15
  """Block Storage Service action implementations"""
16
16
 
17
- from cinderclient import api_versions
17
+ from openstack import utils as sdk_utils
18
18
  from osc_lib.command import command
19
19
  from osc_lib import exceptions
20
- from osc_lib import utils
21
20
 
22
21
  from openstackclient.i18n import _
23
22
 
@@ -33,7 +32,7 @@ class BlockStorageLogLevelList(command.Lister):
33
32
  parser.add_argument(
34
33
  "--host",
35
34
  metavar="<host>",
36
- default="",
35
+ default=None,
37
36
  help=_(
38
37
  "List block storage service log level of specified host "
39
38
  "(name only)"
@@ -42,9 +41,9 @@ class BlockStorageLogLevelList(command.Lister):
42
41
  parser.add_argument(
43
42
  "--service",
44
43
  metavar="<service>",
45
- default="",
44
+ default=None,
46
45
  choices=(
47
- '',
46
+ None,
48
47
  '*',
49
48
  'cinder-api',
50
49
  'cinder-volume',
@@ -59,13 +58,13 @@ class BlockStorageLogLevelList(command.Lister):
59
58
  parser.add_argument(
60
59
  "--log-prefix",
61
60
  metavar="<log-prefix>",
62
- default="",
61
+ default=None,
63
62
  help="Prefix for the log, e.g. 'sqlalchemy'",
64
63
  )
65
64
  return parser
66
65
 
67
66
  def take_action(self, parsed_args):
68
- service_client = self.app.client_manager.volume
67
+ volume_client = self.app.client_manager.sdk_connection.volume
69
68
  columns = [
70
69
  "Binary",
71
70
  "Host",
@@ -73,29 +72,24 @@ class BlockStorageLogLevelList(command.Lister):
73
72
  "Level",
74
73
  ]
75
74
 
76
- if service_client.api_version < api_versions.APIVersion('3.32'):
75
+ if not sdk_utils.supports_microversion(volume_client, '3.32'):
77
76
  msg = _(
78
77
  "--os-volume-api-version 3.32 or greater is required to "
79
78
  "support the 'block storage log level list' command"
80
79
  )
81
80
  raise exceptions.CommandError(msg)
82
81
 
83
- data = service_client.services.get_log_levels(
82
+ data = []
83
+ for entry in volume_client.get_service_log_levels(
84
84
  binary=parsed_args.service,
85
85
  server=parsed_args.host,
86
86
  prefix=parsed_args.log_prefix,
87
- )
87
+ ):
88
+ entry_levels = sorted(entry.levels.items(), key=lambda x: x[0])
89
+ for prefix, level in entry_levels:
90
+ data.append((entry.binary, entry.host, prefix, level))
88
91
 
89
- return (
90
- columns,
91
- (
92
- utils.get_item_properties(
93
- s,
94
- columns,
95
- )
96
- for s in data
97
- ),
98
- )
92
+ return (columns, data)
99
93
 
100
94
 
101
95
  class BlockStorageLogLevelSet(command.Command):
@@ -111,12 +105,12 @@ class BlockStorageLogLevelSet(command.Command):
111
105
  metavar="<log-level>",
112
106
  choices=('INFO', 'WARNING', 'ERROR', 'DEBUG'),
113
107
  type=str.upper,
114
- help=_("Desired log level."),
108
+ help=_("Desired log level"),
115
109
  )
116
110
  parser.add_argument(
117
111
  "--host",
118
112
  metavar="<host>",
119
- default="",
113
+ default=None,
120
114
  help=_(
121
115
  "Set block storage service log level of specified host "
122
116
  "(name only)"
@@ -125,9 +119,9 @@ class BlockStorageLogLevelSet(command.Command):
125
119
  parser.add_argument(
126
120
  "--service",
127
121
  metavar="<service>",
128
- default="",
122
+ default=None,
129
123
  choices=(
130
- '',
124
+ None,
131
125
  '*',
132
126
  'cinder-api',
133
127
  'cinder-volume',
@@ -142,22 +136,22 @@ class BlockStorageLogLevelSet(command.Command):
142
136
  parser.add_argument(
143
137
  "--log-prefix",
144
138
  metavar="<log-prefix>",
145
- default="",
139
+ default=None,
146
140
  help="Prefix for the log, e.g. 'sqlalchemy'",
147
141
  )
148
142
  return parser
149
143
 
150
144
  def take_action(self, parsed_args):
151
- service_client = self.app.client_manager.volume
145
+ volume_client = self.app.client_manager.sdk_connection.volume
152
146
 
153
- if service_client.api_version < api_versions.APIVersion('3.32'):
147
+ if not sdk_utils.supports_microversion(volume_client, '3.32'):
154
148
  msg = _(
155
149
  "--os-volume-api-version 3.32 or greater is required to "
156
150
  "support the 'block storage log level set' command"
157
151
  )
158
152
  raise exceptions.CommandError(msg)
159
153
 
160
- service_client.services.set_log_levels(
154
+ volume_client.set_service_log_levels(
161
155
  level=parsed_args.level,
162
156
  binary=parsed_args.service,
163
157
  server=parsed_args.host,
@@ -14,37 +14,72 @@
14
14
 
15
15
  """Service action implementations"""
16
16
 
17
- from cinderclient import api_versions
17
+ from openstack import utils as sdk_utils
18
+ from osc_lib.command import command
19
+ from osc_lib import exceptions
18
20
  from osc_lib import utils
19
21
 
20
- from openstackclient.volume.v2 import service as service_v2
22
+ from openstackclient.i18n import _
21
23
 
22
24
 
23
- class ListService(service_v2.ListService):
25
+ class ListService(command.Lister):
26
+ _description = _("List service command")
27
+
28
+ def get_parser(self, prog_name):
29
+ parser = super().get_parser(prog_name)
30
+ parser.add_argument(
31
+ "--host",
32
+ metavar="<host>",
33
+ help=_("List services on specified host (name only)"),
34
+ )
35
+ parser.add_argument(
36
+ "--service",
37
+ metavar="<service>",
38
+ help=_("List only specified service (name only)"),
39
+ )
40
+ parser.add_argument(
41
+ "--long",
42
+ action="store_true",
43
+ default=False,
44
+ help=_("List additional fields in output"),
45
+ )
46
+ return parser
47
+
24
48
  def take_action(self, parsed_args):
25
- service_client = self.app.client_manager.volume
49
+ volume_client = self.app.client_manager.sdk_connection.volume
26
50
 
27
- columns = [
51
+ columns: tuple[str, ...] = (
52
+ "binary",
53
+ "host",
54
+ "availability_zone",
55
+ "status",
56
+ "state",
57
+ "updated_at",
58
+ )
59
+ column_names: tuple[str, ...] = (
28
60
  "Binary",
29
61
  "Host",
30
62
  "Zone",
31
63
  "Status",
32
64
  "State",
33
65
  "Updated At",
34
- ]
66
+ )
35
67
 
36
- if service_client.api_version >= api_versions.APIVersion('3.7'):
37
- columns.append("Cluster")
38
- if service_client.api_version >= api_versions.APIVersion('3.49'):
39
- columns.append("Backend State")
68
+ if sdk_utils.supports_microversion(volume_client, '3.7'):
69
+ columns += ("cluster",)
70
+ column_names += ("Cluster",)
71
+ if sdk_utils.supports_microversion(volume_client, '3.49'):
72
+ columns += ("backend_state",)
73
+ column_names += ("Backend State",)
40
74
  if parsed_args.long:
41
- columns.append("Disabled Reason")
75
+ columns += ("disabled_reason",)
76
+ column_names += ("Disabled Reason",)
42
77
 
43
- data = service_client.services.list(
44
- parsed_args.host, parsed_args.service
78
+ data = volume_client.services(
79
+ host=parsed_args.host, binary=parsed_args.service
45
80
  )
46
81
  return (
47
- columns,
82
+ column_names,
48
83
  (
49
84
  utils.get_item_properties(
50
85
  s,
@@ -53,3 +88,59 @@ class ListService(service_v2.ListService):
53
88
  for s in data
54
89
  ),
55
90
  )
91
+
92
+
93
+ class SetService(command.Command):
94
+ _description = _("Set volume service properties")
95
+
96
+ def get_parser(self, prog_name):
97
+ parser = super().get_parser(prog_name)
98
+ parser.add_argument(
99
+ "host",
100
+ metavar="<host>",
101
+ help=_("Name of host"),
102
+ )
103
+ parser.add_argument(
104
+ "service",
105
+ metavar="<service>",
106
+ help=_("Name of service (Binary name)"),
107
+ )
108
+ enabled_group = parser.add_mutually_exclusive_group()
109
+ enabled_group.add_argument(
110
+ "--enable", action="store_true", help=_("Enable volume service")
111
+ )
112
+ enabled_group.add_argument(
113
+ "--disable", action="store_true", help=_("Disable volume service")
114
+ )
115
+ parser.add_argument(
116
+ "--disable-reason",
117
+ metavar="<reason>",
118
+ help=_(
119
+ "Reason for disabling the service "
120
+ "(should be used with --disable option)"
121
+ ),
122
+ )
123
+ return parser
124
+
125
+ def take_action(self, parsed_args):
126
+ if parsed_args.disable_reason and not parsed_args.disable:
127
+ msg = _(
128
+ "Cannot specify option --disable-reason without "
129
+ "--disable specified."
130
+ )
131
+ raise exceptions.CommandError(msg)
132
+
133
+ volume_client = self.app.client_manager.sdk_connection.volume
134
+
135
+ service = volume_client.find_service(
136
+ parsed_args.service, ignore_missing=False, host=parsed_args.host
137
+ )
138
+
139
+ if parsed_args.enable:
140
+ service.enable(volume_client)
141
+
142
+ if parsed_args.disable:
143
+ service.disable(
144
+ volume_client,
145
+ reason=parsed_args.disable_reason,
146
+ )