python-openstackclient 6.3.0__py3-none-any.whl → 6.5.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 (162) hide show
  1. openstackclient/common/availability_zone.py +4 -4
  2. openstackclient/common/pagination.py +82 -0
  3. openstackclient/compute/v2/flavor.py +2 -16
  4. openstackclient/compute/v2/hypervisor.py +2 -21
  5. openstackclient/compute/v2/keypair.py +2 -9
  6. openstackclient/compute/v2/server.py +220 -131
  7. openstackclient/compute/v2/server_event.py +30 -19
  8. openstackclient/compute/v2/server_group.py +2 -23
  9. openstackclient/compute/v2/server_migration.py +2 -22
  10. openstackclient/compute/v2/usage.py +4 -6
  11. openstackclient/identity/v3/mapping.py +25 -3
  12. openstackclient/identity/v3/policy.py +3 -1
  13. openstackclient/image/v2/cache.py +218 -0
  14. openstackclient/image/v2/image.py +40 -17
  15. openstackclient/image/v2/metadef_namespaces.py +25 -21
  16. openstackclient/image/v2/metadef_objects.py +189 -0
  17. openstackclient/image/v2/metadef_properties.py +284 -0
  18. openstackclient/network/utils.py +100 -0
  19. openstackclient/network/v2/default_security_group_rule.py +418 -0
  20. openstackclient/network/v2/local_ip_association.py +1 -1
  21. openstackclient/network/v2/ndp_proxy.py +7 -3
  22. openstackclient/network/v2/network.py +2 -2
  23. openstackclient/network/v2/port.py +65 -19
  24. openstackclient/network/v2/security_group_rule.py +18 -111
  25. openstackclient/network/v2/subnet.py +1 -0
  26. openstackclient/object/v1/container.py +2 -12
  27. openstackclient/object/v1/object.py +2 -11
  28. openstackclient/tests/functional/base.py +13 -6
  29. openstackclient/tests/functional/identity/v3/test_role.py +11 -3
  30. openstackclient/tests/functional/network/v2/common.py +7 -1
  31. openstackclient/tests/functional/network/v2/test_address_group.py +2 -4
  32. openstackclient/tests/functional/network/v2/test_address_scope.py +0 -6
  33. openstackclient/tests/functional/network/v2/test_default_security_group_rule.py +67 -0
  34. openstackclient/tests/functional/network/v2/test_floating_ip.py +3 -6
  35. openstackclient/tests/functional/network/v2/test_ip_availability.py +3 -8
  36. openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py +3 -4
  37. openstackclient/tests/functional/network/v2/test_local_ip.py +2 -4
  38. openstackclient/tests/functional/network/v2/test_network.py +18 -17
  39. openstackclient/tests/functional/network/v2/test_network_agent.py +24 -21
  40. openstackclient/tests/functional/network/v2/test_network_flavor.py +0 -6
  41. openstackclient/tests/functional/network/v2/test_network_flavor_profile.py +0 -6
  42. openstackclient/tests/functional/network/v2/test_network_meter.py +6 -6
  43. openstackclient/tests/functional/network/v2/test_network_meter_rule.py +7 -8
  44. openstackclient/tests/functional/network/v2/test_network_ndp_proxy.py +1 -3
  45. openstackclient/tests/functional/network/v2/test_network_qos_policy.py +4 -4
  46. openstackclient/tests/functional/network/v2/test_network_qos_rule.py +16 -20
  47. openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py +4 -4
  48. openstackclient/tests/functional/network/v2/test_network_rbac.py +1 -4
  49. openstackclient/tests/functional/network/v2/test_network_segment.py +7 -12
  50. openstackclient/tests/functional/network/v2/test_network_segment_range.py +3 -4
  51. openstackclient/tests/functional/network/v2/test_network_service_provider.py +2 -4
  52. openstackclient/tests/functional/network/v2/test_network_trunk.py +3 -3
  53. openstackclient/tests/functional/network/v2/test_port.py +2 -8
  54. openstackclient/tests/functional/network/v2/test_router.py +0 -6
  55. openstackclient/tests/functional/network/v2/test_security_group.py +1 -4
  56. openstackclient/tests/functional/network/v2/test_security_group_rule.py +1 -4
  57. openstackclient/tests/functional/network/v2/test_subnet.py +4 -22
  58. openstackclient/tests/functional/network/v2/test_subnet_pool.py +0 -6
  59. openstackclient/tests/unit/common/test_availability_zone.py +28 -30
  60. openstackclient/tests/unit/common/test_extension.py +1 -4
  61. openstackclient/tests/unit/common/test_limits.py +2 -4
  62. openstackclient/tests/unit/common/test_project_cleanup.py +3 -10
  63. openstackclient/tests/unit/common/test_quota.py +18 -24
  64. openstackclient/tests/unit/compute/v2/fakes.py +24 -11
  65. openstackclient/tests/unit/compute/v2/test_agent.py +1 -1
  66. openstackclient/tests/unit/compute/v2/test_aggregate.py +62 -72
  67. openstackclient/tests/unit/compute/v2/test_console.py +18 -30
  68. openstackclient/tests/unit/compute/v2/test_flavor.py +85 -89
  69. openstackclient/tests/unit/compute/v2/test_host.py +12 -19
  70. openstackclient/tests/unit/compute/v2/test_hypervisor.py +23 -25
  71. openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py +2 -6
  72. openstackclient/tests/unit/compute/v2/test_keypair.py +25 -39
  73. openstackclient/tests/unit/compute/v2/test_server.py +316 -365
  74. openstackclient/tests/unit/compute/v2/test_server_backup.py +5 -17
  75. openstackclient/tests/unit/compute/v2/test_server_event.py +23 -25
  76. openstackclient/tests/unit/compute/v2/test_server_group.py +41 -33
  77. openstackclient/tests/unit/compute/v2/test_server_image.py +6 -18
  78. openstackclient/tests/unit/compute/v2/test_server_migration.py +45 -45
  79. openstackclient/tests/unit/compute/v2/test_server_volume.py +15 -31
  80. openstackclient/tests/unit/compute/v2/test_service.py +51 -56
  81. openstackclient/tests/unit/compute/v2/test_usage.py +10 -13
  82. openstackclient/tests/unit/fakes.py +4 -0
  83. openstackclient/tests/unit/identity/v3/test_mappings.py +9 -4
  84. openstackclient/tests/unit/identity/v3/test_trust.py +0 -2
  85. openstackclient/tests/unit/image/v1/fakes.py +2 -1
  86. openstackclient/tests/unit/image/v1/test_image.py +1 -1
  87. openstackclient/tests/unit/image/v2/fakes.py +82 -0
  88. openstackclient/tests/unit/image/v2/test_cache.py +214 -0
  89. openstackclient/tests/unit/image/v2/test_image.py +62 -4
  90. openstackclient/tests/unit/image/v2/test_metadef_namespaces.py +5 -19
  91. openstackclient/tests/unit/image/v2/test_metadef_objects.py +162 -0
  92. openstackclient/tests/unit/image/v2/test_metadef_properties.py +227 -0
  93. openstackclient/tests/unit/integ/cli/test_shell.py +0 -2
  94. openstackclient/tests/unit/network/test_common.py +3 -3
  95. openstackclient/tests/unit/network/v2/fakes.py +1 -0
  96. openstackclient/tests/unit/network/v2/test_default_security_group_rule.py +1133 -0
  97. openstackclient/tests/unit/network/v2/test_floating_ip_compute.py +5 -13
  98. openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py +1 -9
  99. openstackclient/tests/unit/network/v2/test_network.py +33 -0
  100. openstackclient/tests/unit/network/v2/test_network_compute.py +5 -11
  101. openstackclient/tests/unit/network/v2/test_network_trunk.py +6 -8
  102. openstackclient/tests/unit/network/v2/test_port.py +83 -38
  103. openstackclient/tests/unit/network/v2/test_security_group_compute.py +7 -15
  104. openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +19 -27
  105. openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +3 -6
  106. openstackclient/tests/unit/network/v2/test_subnet.py +92 -0
  107. openstackclient/tests/unit/network/v2/test_subnet_pool.py +11 -13
  108. openstackclient/tests/unit/test_shell.py +1 -7
  109. openstackclient/tests/unit/utils.py +10 -4
  110. openstackclient/tests/unit/volume/v1/fakes.py +7 -1
  111. openstackclient/tests/unit/volume/v1/test_qos_specs.py +2 -2
  112. openstackclient/tests/unit/volume/v1/test_service.py +1 -1
  113. openstackclient/tests/unit/volume/v1/test_transfer_request.py +2 -2
  114. openstackclient/tests/unit/volume/v1/test_type.py +2 -4
  115. openstackclient/tests/unit/volume/v1/test_volume.py +5 -7
  116. openstackclient/tests/unit/volume/v1/test_volume_backup.py +4 -4
  117. openstackclient/tests/unit/volume/v2/fakes.py +32 -12
  118. openstackclient/tests/unit/volume/v2/test_backup_record.py +1 -1
  119. openstackclient/tests/unit/volume/v2/test_consistency_group.py +4 -6
  120. openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py +2 -4
  121. openstackclient/tests/unit/volume/v2/test_qos_specs.py +2 -2
  122. openstackclient/tests/unit/volume/v2/test_service.py +1 -1
  123. openstackclient/tests/unit/volume/v2/test_volume.py +78 -16
  124. openstackclient/tests/unit/volume/v2/test_volume_backend.py +10 -22
  125. openstackclient/tests/unit/volume/v2/test_volume_backup.py +76 -89
  126. openstackclient/tests/unit/volume/v2/test_volume_host.py +1 -1
  127. openstackclient/tests/unit/volume/v2/test_volume_snapshot.py +5 -7
  128. openstackclient/tests/unit/volume/v2/test_volume_transfer_request.py +4 -8
  129. openstackclient/tests/unit/volume/v2/test_volume_type.py +164 -24
  130. openstackclient/tests/unit/volume/v3/fakes.py +91 -15
  131. openstackclient/tests/unit/volume/v3/test_block_storage_cleanup.py +3 -7
  132. openstackclient/tests/unit/volume/v3/test_block_storage_cluster.py +11 -31
  133. openstackclient/tests/unit/volume/v3/test_block_storage_log_level.py +6 -16
  134. openstackclient/tests/unit/volume/v3/test_block_storage_manage.py +219 -157
  135. openstackclient/tests/unit/volume/v3/test_block_storage_resource_filter.py +32 -23
  136. openstackclient/tests/unit/volume/v3/test_volume.py +50 -48
  137. openstackclient/tests/unit/volume/v3/test_volume_attachment.py +17 -47
  138. openstackclient/tests/unit/volume/v3/test_volume_group.py +23 -65
  139. openstackclient/tests/unit/volume/v3/test_volume_group_snapshot.py +88 -77
  140. openstackclient/tests/unit/volume/v3/test_volume_group_type.py +14 -42
  141. openstackclient/tests/unit/volume/v3/test_volume_message.py +10 -28
  142. openstackclient/volume/v1/volume.py +2 -14
  143. openstackclient/volume/v2/volume.py +30 -15
  144. openstackclient/volume/v2/volume_backend.py +10 -18
  145. openstackclient/volume/v2/volume_backup.py +18 -15
  146. openstackclient/volume/v2/volume_snapshot.py +2 -12
  147. openstackclient/volume/v2/volume_type.py +211 -14
  148. openstackclient/volume/v3/block_storage_manage.py +72 -11
  149. openstackclient/volume/v3/block_storage_resource_filter.py +33 -11
  150. openstackclient/volume/v3/volume_attachment.py +2 -14
  151. openstackclient/volume/v3/volume_group_snapshot.py +27 -27
  152. openstackclient/volume/v3/volume_message.py +2 -13
  153. {python_openstackclient-6.3.0.dist-info → python_openstackclient-6.5.0.dist-info}/AUTHORS +11 -0
  154. {python_openstackclient-6.3.0.dist-info → python_openstackclient-6.5.0.dist-info}/METADATA +6 -5
  155. {python_openstackclient-6.3.0.dist-info → python_openstackclient-6.5.0.dist-info}/RECORD +160 -151
  156. {python_openstackclient-6.3.0.dist-info → python_openstackclient-6.5.0.dist-info}/entry_points.txt +23 -5
  157. python_openstackclient-6.5.0.dist-info/pbr.json +1 -0
  158. openstackclient/tests/unit/common/test_parseractions.py +0 -233
  159. python_openstackclient-6.3.0.dist-info/pbr.json +0 -1
  160. {python_openstackclient-6.3.0.dist-info → python_openstackclient-6.5.0.dist-info}/LICENSE +0 -0
  161. {python_openstackclient-6.3.0.dist-info → python_openstackclient-6.5.0.dist-info}/WHEEL +0 -0
  162. {python_openstackclient-6.3.0.dist-info → python_openstackclient-6.5.0.dist-info}/top_level.txt +0 -0
@@ -32,13 +32,12 @@ from osc_lib.cli import parseractions
32
32
  from osc_lib.command import command
33
33
  from osc_lib import exceptions
34
34
  from osc_lib import utils
35
- from oslo_utils import strutils
36
35
 
36
+ from openstackclient.common import pagination
37
37
  from openstackclient.i18n import _
38
38
  from openstackclient.identity import common as identity_common
39
39
  from openstackclient.network import common as network_common
40
40
 
41
-
42
41
  LOG = logging.getLogger(__name__)
43
42
 
44
43
  IMAGE_STRING_FOR_BFV = 'N/A (booted from volume)'
@@ -150,16 +149,13 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
150
149
  # Some commands using this routine were originally implemented with the
151
150
  # nova python wrappers, and were later migrated to use the SDK. Map the
152
151
  # SDK's property names to the original property names to maintain backward
153
- # compatibility for existing users. Data is duplicated under both the old
154
- # and new name so users can consume the data by either name.
152
+ # compatibility for existing users.
155
153
  column_map = {
156
154
  'access_ipv4': 'accessIPv4',
157
155
  'access_ipv6': 'accessIPv6',
158
156
  'admin_password': 'adminPass',
159
- 'admin_password': 'adminPass',
160
- 'volumes': 'os-extended-volumes:volumes_attached',
157
+ 'attached_volumes': 'volumes_attached',
161
158
  'availability_zone': 'OS-EXT-AZ:availability_zone',
162
- 'block_device_mapping': 'block_device_mapping_v2',
163
159
  'compute_host': 'OS-EXT-SRV-ATTR:host',
164
160
  'created_at': 'created',
165
161
  'disk_config': 'OS-DCF:diskConfig',
@@ -169,7 +165,6 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
169
165
  'fault': 'fault',
170
166
  'hostname': 'OS-EXT-SRV-ATTR:hostname',
171
167
  'hypervisor_hostname': 'OS-EXT-SRV-ATTR:hypervisor_hostname',
172
- 'image_id': 'imageRef',
173
168
  'instance_name': 'OS-EXT-SRV-ATTR:instance_name',
174
169
  'is_locked': 'locked',
175
170
  'kernel_id': 'OS-EXT-SRV-ATTR:kernel_id',
@@ -180,21 +175,56 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
180
175
  'ramdisk_id': 'OS-EXT-SRV-ATTR:ramdisk_id',
181
176
  'reservation_id': 'OS-EXT-SRV-ATTR:reservation_id',
182
177
  'root_device_name': 'OS-EXT-SRV-ATTR:root_device_name',
183
- 'scheduler_hints': 'OS-SCH-HNT:scheduler_hints',
184
178
  'task_state': 'OS-EXT-STS:task_state',
185
179
  'terminated_at': 'OS-SRV-USG:terminated_at',
186
180
  'updated_at': 'updated',
187
181
  'user_data': 'OS-EXT-SRV-ATTR:user_data',
188
182
  'vm_state': 'OS-EXT-STS:vm_state',
189
183
  }
184
+ # Some columns returned by openstacksdk should not be shown because they're
185
+ # either irrelevant or duplicates
186
+ ignored_columns = {
187
+ # computed columns
188
+ 'interface_ip',
189
+ 'location',
190
+ 'private_v4',
191
+ 'private_v6',
192
+ 'public_v4',
193
+ 'public_v6',
194
+ # create-only columns
195
+ 'block_device_mapping',
196
+ 'image_id',
197
+ 'max_count',
198
+ 'min_count',
199
+ 'scheduler_hints',
200
+ # aliases
201
+ 'volumes',
202
+ # unnecessary
203
+ 'links',
204
+ }
205
+ # Some columns are only present in certain responses and should not be
206
+ # shown otherwise.
207
+ optional_columns = {
208
+ 'admin_password', # removed in 2.14
209
+ 'fault', # only present in errored servers
210
+ 'flavor_id', # removed in 2.47
211
+ 'networks', # only present in create responses
212
+ 'security_groups', # only present in create, detail responses
213
+ }
190
214
 
191
- info.update(
192
- {
193
- column_map[column]: data
194
- for column, data in info.items()
195
- if column in column_map
196
- }
197
- )
215
+ data = {}
216
+ for key, value in info.items():
217
+ if key in ignored_columns:
218
+ continue
219
+
220
+ if key in optional_columns:
221
+ if info[key] is None:
222
+ continue
223
+
224
+ alias = column_map.get(key)
225
+ data[alias or key] = value
226
+
227
+ info = data
198
228
 
199
229
  # Convert the image blob to a name
200
230
  image_info = info.get('image', {})
@@ -215,46 +245,57 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
215
245
  # Convert the flavor blob to a name
216
246
  flavor_info = info.get('flavor', {})
217
247
  # Microversion 2.47 puts the embedded flavor into the server response
218
- # body but omits the id, so if not present we just expose the flavor
219
- # dict in the server output.
220
- if 'id' in flavor_info:
248
+ # body. The presence of the 'original_name' attribute indicates this.
249
+ if flavor_info.get('original_name') is None: # microversion < 2.47
221
250
  flavor_id = flavor_info.get('id', '')
222
251
  try:
223
252
  flavor = utils.find_resource(compute_client.flavors, flavor_id)
224
253
  info['flavor'] = "%s (%s)" % (flavor.name, flavor_id)
225
254
  except Exception:
226
255
  info['flavor'] = flavor_id
227
- else:
256
+ else: # microversion >= 2.47
228
257
  info['flavor'] = format_columns.DictColumn(flavor_info)
229
258
 
230
- if 'os-extended-volumes:volumes_attached' in info:
259
+ # there's a lot of redundant information in BDMs - strip it
260
+ if 'volumes_attached' in info:
231
261
  info.update(
232
262
  {
233
263
  'volumes_attached': format_columns.ListDictColumn(
234
- info.pop('os-extended-volumes:volumes_attached')
264
+ [
265
+ {
266
+ k: v
267
+ for k, v in volume.items()
268
+ if v is not None and k != 'location'
269
+ }
270
+ for volume in info.pop('volumes_attached') or []
271
+ ]
235
272
  )
236
273
  }
237
274
  )
275
+
238
276
  if 'security_groups' in info:
239
277
  info.update(
240
278
  {
241
279
  'security_groups': format_columns.ListDictColumn(
242
- info.pop('security_groups')
280
+ info.pop('security_groups'),
243
281
  )
244
282
  }
245
283
  )
284
+
246
285
  if 'tags' in info:
247
286
  info.update({'tags': format_columns.ListColumn(info.pop('tags'))})
248
287
 
249
- # NOTE(dtroyer): novaclient splits these into separate entries...
250
- # Format addresses in a useful way
251
- info['addresses'] = (
252
- AddressesColumn(info['addresses'])
253
- if 'addresses' in info
254
- else format_columns.DictListColumn(info.get('networks'))
255
- )
288
+ # Map 'networks' to 'addresses', if present. Note that the 'networks' key
289
+ # is used for create responses, otherwise it's 'addresses'. We know it'll
290
+ # be set because this is one of our optional columns.
291
+ if 'networks' in info:
292
+ info['addresses'] = format_columns.DictListColumn(
293
+ info.pop('networks', {}),
294
+ )
295
+ else:
296
+ info['addresses'] = AddressesColumn(info.get('addresses', {}))
256
297
 
257
- # Map 'metadata' field to 'properties'
298
+ # Map 'metadata' field to 'properties' and format
258
299
  info['properties'] = format_columns.DictColumn(info.pop('metadata'))
259
300
 
260
301
  # Migrate tenant_id to project_id naming
@@ -267,12 +308,33 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
267
308
  info['OS-EXT-STS:power_state']
268
309
  )
269
310
 
270
- # Remove values that are long and not too useful
271
- info.pop('links', None)
272
-
273
311
  return info
274
312
 
275
313
 
314
+ def bool_from_str(value, strict=False):
315
+ true_strings = ('1', 't', 'true', 'on', 'y', 'yes')
316
+ false_strings = ('0', 'f', 'false', 'off', 'n', 'no')
317
+
318
+ if isinstance(value, bool):
319
+ return value
320
+
321
+ lowered = value.strip().lower()
322
+ if lowered in true_strings:
323
+ return True
324
+ elif lowered in false_strings or not strict:
325
+ return False
326
+
327
+ msg = _(
328
+ "Unrecognized value '%(value)s'; acceptable values are: %(valid)s"
329
+ ) % {
330
+ 'value': value,
331
+ 'valid': ', '.join(
332
+ f"'{s}'" for s in sorted(true_strings + false_strings)
333
+ ),
334
+ }
335
+ raise ValueError(msg)
336
+
337
+
276
338
  def boolenv(*vars, default=False):
277
339
  """Search for the first defined of possibly many bool-like env vars.
278
340
 
@@ -287,7 +349,7 @@ def boolenv(*vars, default=False):
287
349
  for v in vars:
288
350
  value = os.environ.get(v, None)
289
351
  if value:
290
- return strutils.bool_from_string(value)
352
+ return bool_from_str(value)
291
353
  return default
292
354
 
293
355
 
@@ -818,7 +880,7 @@ class NICAction(argparse.Action):
818
880
  "Invalid argument %s; characters ',' and '=' are not "
819
881
  "allowed"
820
882
  )
821
- raise argparse.ArgumentTypeError(msg % values)
883
+ raise argparse.ArgumentError(self, msg % values)
822
884
 
823
885
  values = '='.join([self.key, values])
824
886
  else:
@@ -846,7 +908,7 @@ class NICAction(argparse.Action):
846
908
  "'net-id=net-uuid,port-id=port-uuid,v4-fixed-ip=ip-addr,"
847
909
  "v6-fixed-ip=ip-addr,tag=tag'"
848
910
  )
849
- raise argparse.ArgumentTypeError(msg % values)
911
+ raise argparse.ArgumentError(self, msg % values)
850
912
 
851
913
  info[k] = v
852
914
 
@@ -855,7 +917,7 @@ class NICAction(argparse.Action):
855
917
  'Invalid argument %s; either network or port should be '
856
918
  'specified but not both'
857
919
  )
858
- raise argparse.ArgumentTypeError(msg % values)
920
+ raise argparse.ArgumenteError(self, msg % values)
859
921
 
860
922
  getattr(namespace, self.dest).append(info)
861
923
 
@@ -873,7 +935,7 @@ class BDMLegacyAction(argparse.Action):
873
935
  "Invalid argument %s; argument must be of form "
874
936
  "'dev-name=id[:type[:size[:delete-on-terminate]]]'"
875
937
  )
876
- raise argparse.ArgumentTypeError(msg % values)
938
+ raise argparse.ArgumentError(self, msg % values)
877
939
 
878
940
  mapping = {
879
941
  'device_name': dev_name,
@@ -890,7 +952,7 @@ class BDMLegacyAction(argparse.Action):
890
952
  "Invalid argument %s; 'type' must be one of: volume, "
891
953
  "snapshot, image"
892
954
  )
893
- raise argparse.ArgumentTypeError(msg % values)
955
+ raise argparse.ArgumentError(self, msg % values)
894
956
 
895
957
  mapping['source_type'] = dev_map[1]
896
958
 
@@ -943,12 +1005,13 @@ class BDMAction(parseractions.MultiKeyValueAction):
943
1005
  "Invalid keys %(invalid_keys)s specified.\n"
944
1006
  "Valid keys are: %(valid_keys)s"
945
1007
  )
946
- raise argparse.ArgumentTypeError(
1008
+ raise argparse.ArgumentError(
1009
+ self,
947
1010
  msg
948
1011
  % {
949
1012
  'invalid_keys': ', '.join(invalid_keys),
950
1013
  'valid_keys': ', '.join(valid_keys),
951
- }
1014
+ },
952
1015
  )
953
1016
 
954
1017
  missing_keys = [k for k in self.required_keys if k not in keys]
@@ -957,12 +1020,13 @@ class BDMAction(parseractions.MultiKeyValueAction):
957
1020
  "Missing required keys %(missing_keys)s.\n"
958
1021
  "Required keys are: %(required_keys)s"
959
1022
  )
960
- raise argparse.ArgumentTypeError(
1023
+ raise argparse.ArgumentError(
1024
+ self,
961
1025
  msg
962
1026
  % {
963
1027
  'missing_keys': ', '.join(missing_keys),
964
1028
  'required_keys': ', '.join(self.required_keys),
965
- }
1029
+ },
966
1030
  )
967
1031
 
968
1032
  def __call__(self, parser, namespace, values, option_string=None):
@@ -1313,10 +1377,19 @@ class CreateServer(command.ShowOne):
1313
1377
  '(supported by --os-compute-api-version 2.74 or above)'
1314
1378
  ),
1315
1379
  )
1380
+ parser.add_argument(
1381
+ '--server-group',
1382
+ metavar='<server-group>',
1383
+ help=_(
1384
+ "Server group to create the server within "
1385
+ "(this is an alias for '--hint group=<server-group-id>')"
1386
+ ),
1387
+ )
1316
1388
  parser.add_argument(
1317
1389
  '--hint',
1318
1390
  metavar='<key=value>',
1319
1391
  action=parseractions.KeyValueAppendAction,
1392
+ dest='hints',
1320
1393
  default={},
1321
1394
  help=_('Hints for the scheduler'),
1322
1395
  )
@@ -1553,8 +1626,8 @@ class CreateServer(command.ShowOne):
1553
1626
  if parsed_args.description:
1554
1627
  if compute_client.api_version < api_versions.APIVersion("2.19"):
1555
1628
  msg = _(
1556
- "Description is not supported for "
1557
- "--os-compute-api-version less than 2.19"
1629
+ '--os-compute-api-version 2.19 or greater is '
1630
+ 'required to support the --description option'
1558
1631
  )
1559
1632
  raise exceptions.CommandError(msg)
1560
1633
 
@@ -1580,6 +1653,12 @@ class CreateServer(command.ShowOne):
1580
1653
  ]
1581
1654
  elif parsed_args.boot_from_volume:
1582
1655
  # Tell nova to create a root volume from the image provided.
1656
+ if not image:
1657
+ msg = _(
1658
+ "An image (--image or --image-property) is required "
1659
+ "to support --boot-from-volume option"
1660
+ )
1661
+ raise exceptions.CommandError(msg)
1583
1662
  block_device_mapping_v2 = [
1584
1663
  {
1585
1664
  'uuid': image.id,
@@ -1713,8 +1792,9 @@ class CreateServer(command.ShowOne):
1713
1792
 
1714
1793
  if 'delete_on_termination' in mapping:
1715
1794
  try:
1716
- value = strutils.bool_from_string(
1717
- mapping['delete_on_termination'], strict=True
1795
+ value = bool_from_str(
1796
+ mapping['delete_on_termination'],
1797
+ strict=True,
1718
1798
  )
1719
1799
  except ValueError:
1720
1800
  msg = _(
@@ -1828,13 +1908,20 @@ class CreateServer(command.ShowOne):
1828
1908
  security_group_names.append(sg['name'])
1829
1909
 
1830
1910
  hints = {}
1831
- for key, values in parsed_args.hint.items():
1911
+ for key, values in parsed_args.hints.items():
1832
1912
  # only items with multiple values will result in a list
1833
1913
  if len(values) == 1:
1834
1914
  hints[key] = values[0]
1835
1915
  else:
1836
1916
  hints[key] = values
1837
1917
 
1918
+ if parsed_args.server_group:
1919
+ server_group_obj = utils.find_resource(
1920
+ compute_client.server_groups,
1921
+ parsed_args.server_group,
1922
+ )
1923
+ hints['group'] = server_group_obj.id
1924
+
1838
1925
  if isinstance(parsed_args.config_drive, bool):
1839
1926
  # NOTE(stephenfin): The API doesn't accept False as a value :'(
1840
1927
  config_drive = parsed_args.config_drive or None
@@ -1957,9 +2044,8 @@ class CreateServer(command.ShowOne):
1957
2044
  ):
1958
2045
  self.app.stdout.write('\n')
1959
2046
  else:
1960
- LOG.error('Error creating server: %s', parsed_args.server_name)
1961
- self.app.stdout.write(_('Error creating server\n'))
1962
- raise SystemExit
2047
+ msg = _('Error creating server: %s') % parsed_args.server_name
2048
+ raise exceptions.CommandError(msg)
1963
2049
 
1964
2050
  details = _prep_server_detail(compute_client, image_client, server)
1965
2051
  return zip(*sorted(details.items()))
@@ -2051,17 +2137,52 @@ class DeleteServer(command.Command):
2051
2137
  server_obj.id,
2052
2138
  callback=_show_progress,
2053
2139
  ):
2054
- msg = _('Error deleting server: %s')
2055
- LOG.error(msg, server_obj.id)
2056
- self.app.stdout.write(_('Error deleting server\n'))
2057
- raise SystemExit
2140
+ msg = _('Error deleting server: %s') % server_obj.id
2141
+ raise exceptions.CommandError(msg)
2142
+
2143
+
2144
+ class PercentAction(argparse.Action):
2145
+ def __init__(
2146
+ self,
2147
+ option_strings,
2148
+ dest,
2149
+ nargs=None,
2150
+ const=None,
2151
+ default=None,
2152
+ type=None,
2153
+ choices=None,
2154
+ required=False,
2155
+ help=None,
2156
+ metavar=None,
2157
+ ):
2158
+ if nargs == 0:
2159
+ raise ValueError(
2160
+ 'nargs for store actions must be != 0; if you '
2161
+ 'have nothing to store, actions such as store '
2162
+ 'true or store const may be more appropriate'
2163
+ )
2164
+
2165
+ if const is not None:
2166
+ raise ValueError('const does not make sense for PercentAction')
2058
2167
 
2168
+ super().__init__(
2169
+ option_strings=option_strings,
2170
+ dest=dest,
2171
+ nargs=nargs,
2172
+ const=const,
2173
+ default=default,
2174
+ type=type,
2175
+ choices=choices,
2176
+ required=required,
2177
+ help=help,
2178
+ metavar=metavar,
2179
+ )
2059
2180
 
2060
- def percent_type(x):
2061
- x = int(x)
2062
- if not 0 < x <= 100:
2063
- raise argparse.ArgumentTypeError("Must be between 0 and 100")
2064
- return x
2181
+ def __call__(self, parser, namespace, values, option_string=None):
2182
+ x = int(values)
2183
+ if not 0 < x <= 100:
2184
+ raise argparse.ArgumentError(self, "Must be between 0 and 100")
2185
+ setattr(namespace, self.dest, x)
2065
2186
 
2066
2187
 
2067
2188
  class ListServer(command.Lister):
@@ -2214,7 +2335,7 @@ class ListServer(command.Lister):
2214
2335
  )
2215
2336
  parser.add_argument(
2216
2337
  '--progress',
2217
- type=percent_type,
2338
+ action=PercentAction,
2218
2339
  default=None,
2219
2340
  help=_(
2220
2341
  'Search by progress value (%%) '
@@ -2341,29 +2462,7 @@ class ListServer(command.Lister):
2341
2462
  'Mutually exclusive with "--no-name-lookup|-n" option.'
2342
2463
  ),
2343
2464
  )
2344
- parser.add_argument(
2345
- '--marker',
2346
- metavar='<server>',
2347
- default=None,
2348
- help=_(
2349
- 'The last server of the previous page. Display '
2350
- 'list of servers after marker. Display all servers if not '
2351
- 'specified. When used with ``--deleted``, the marker must '
2352
- 'be an ID, otherwise a name or ID can be used.'
2353
- ),
2354
- )
2355
- parser.add_argument(
2356
- '--limit',
2357
- metavar='<num-servers>',
2358
- type=int,
2359
- default=None,
2360
- help=_(
2361
- "Maximum number of servers to display. If limit equals -1, "
2362
- "all servers will be displayed. If limit is greater than "
2363
- "'osapi_max_limit' option of Nova API, "
2364
- "'osapi_max_limit' will be used instead."
2365
- ),
2366
- )
2465
+ pagination.add_marker_pagination_option_to_parser(parser)
2367
2466
  parser.add_argument(
2368
2467
  '--changes-before',
2369
2468
  metavar='<changes-before>',
@@ -2743,7 +2842,7 @@ class ListServer(command.Lister):
2743
2842
  try:
2744
2843
  # some deployments can have *loads* of images so we only
2745
2844
  # want to list the ones we care about. It would be better
2746
- # to only retrun the *fields* we care about (name) but
2845
+ # to only return the *fields* we care about (name) but
2747
2846
  # glance doesn't support that
2748
2847
  # NOTE(stephenfin): This could result in super long URLs
2749
2848
  # but it seems unlikely to cause issues. Apache supports
@@ -3102,9 +3201,8 @@ revert to release the new server and restart the old one."""
3102
3201
  ):
3103
3202
  self.app.stdout.write(_('Complete\n'))
3104
3203
  else:
3105
- LOG.error(_('Error migrating server: %s'), server.id)
3106
- self.app.stdout.write(_('Error migrating server\n'))
3107
- raise SystemExit
3204
+ msg = _('Error migrating server: %s') % server.id
3205
+ raise exceptions.CommandError(msg)
3108
3206
 
3109
3207
 
3110
3208
  class PauseServer(command.Command):
@@ -3186,9 +3284,8 @@ class RebootServer(command.Command):
3186
3284
  ):
3187
3285
  self.app.stdout.write(_('Complete\n'))
3188
3286
  else:
3189
- LOG.error(_('Error rebooting server: %s'), server_id)
3190
- self.app.stdout.write(_('Error rebooting server\n'))
3191
- raise SystemExit
3287
+ msg = _('Error rebooting server: %s') % server_id
3288
+ raise exceptions.CommandError(msg)
3192
3289
 
3193
3290
 
3194
3291
  class RebuildServer(command.ShowOne):
@@ -3558,9 +3655,8 @@ class RebuildServer(command.ShowOne):
3558
3655
  ):
3559
3656
  self.app.stdout.write(_('Complete\n'))
3560
3657
  else:
3561
- LOG.error(_('Error rebuilding server: %s'), server.id)
3562
- self.app.stdout.write(_('Error rebuilding server\n'))
3563
- raise SystemExit
3658
+ msg = _('Error rebuilding server: %s') % server.id
3659
+ raise exceptions.CommandError(msg)
3564
3660
 
3565
3661
  details = _prep_server_detail(
3566
3662
  compute_client, image_client, server, refresh=False
@@ -3681,9 +3777,8 @@ host."""
3681
3777
  ):
3682
3778
  self.app.stdout.write(_('Complete\n'))
3683
3779
  else:
3684
- LOG.error(_('Error evacuating server: %s'), server.id)
3685
- self.app.stdout.write(_('Error evacuating server\n'))
3686
- raise SystemExit
3780
+ msg = _('Error evacuating server: %s') % server.id
3781
+ raise exceptions.CommandError(msg)
3687
3782
 
3688
3783
  details = _prep_server_detail(
3689
3784
  compute_client, image_client, server, refresh=True
@@ -4017,6 +4112,13 @@ release the new server and restart the old one."""
4017
4112
  compute_client.flavors,
4018
4113
  parsed_args.flavor,
4019
4114
  )
4115
+ if not server.image:
4116
+ self.log.warning(
4117
+ _(
4118
+ "The root disk size in flavor will not be applied "
4119
+ "while booting from a persistent volume."
4120
+ )
4121
+ )
4020
4122
  compute_client.servers.resize(server, flavor)
4021
4123
  if parsed_args.wait:
4022
4124
  if utils.wait_for_status(
@@ -4027,9 +4129,8 @@ release the new server and restart the old one."""
4027
4129
  ):
4028
4130
  self.app.stdout.write(_('Complete\n'))
4029
4131
  else:
4030
- LOG.error(_('Error resizing server: %s'), server.id)
4031
- self.app.stdout.write(_('Error resizing server\n'))
4032
- raise SystemExit
4132
+ msg = _('Error resizing server: %s') % server.id
4133
+ raise exceptions.CommandError(msg)
4033
4134
  elif parsed_args.confirm:
4034
4135
  self.log.warning(
4035
4136
  _(
@@ -4401,6 +4502,7 @@ class ShelveServer(command.Command):
4401
4502
  self.app.stdout.flush()
4402
4503
 
4403
4504
  compute_client = self.app.client_manager.sdk_connection.compute
4505
+ server_ids = []
4404
4506
 
4405
4507
  for server in parsed_args.servers:
4406
4508
  server_obj = compute_client.find_server(
@@ -4410,6 +4512,8 @@ class ShelveServer(command.Command):
4410
4512
  if server_obj.status.lower() in ('shelved', 'shelved_offloaded'):
4411
4513
  continue
4412
4514
 
4515
+ server_ids.append(server_obj.id)
4516
+
4413
4517
  compute_client.shelve_server(server_obj.id)
4414
4518
 
4415
4519
  # if we don't have to wait, either because it was requested explicitly
@@ -4417,56 +4521,44 @@ class ShelveServer(command.Command):
4417
4521
  if not parsed_args.wait and not parsed_args.offload:
4418
4522
  return
4419
4523
 
4420
- for server in parsed_args.servers:
4524
+ for server_id in server_ids:
4421
4525
  # We use osc-lib's wait_for_status since that allows for a callback
4422
4526
  # TODO(stephenfin): We should wait for these in parallel using e.g.
4423
4527
  # https://review.opendev.org/c/openstack/osc-lib/+/762503/
4424
4528
  if not utils.wait_for_status(
4425
4529
  compute_client.get_server,
4426
- server_obj.id,
4530
+ server_id,
4427
4531
  success_status=('shelved', 'shelved_offloaded'),
4428
4532
  callback=_show_progress,
4429
4533
  ):
4430
- LOG.error(_('Error shelving server: %s'), server_obj.id)
4431
- self.app.stdout.write(
4432
- _('Error shelving server: %s\n') % server_obj.id
4433
- )
4434
- raise SystemExit
4534
+ msg = _('Error shelving server: %s') % server_id
4535
+ raise exceptions.CommandError(msg)
4435
4536
 
4436
4537
  if not parsed_args.offload:
4437
4538
  return
4438
4539
 
4439
- for server in parsed_args.servers:
4440
- server_obj = compute_client.find_server(
4441
- server,
4442
- ignore_missing=False,
4443
- )
4540
+ for server_id in server_ids:
4541
+ server_obj = compute_client.get_server(server_id)
4444
4542
  if server_obj.status.lower() == 'shelved_offloaded':
4445
4543
  continue
4446
4544
 
4447
- compute_client.shelve_offload_server(server_obj.id)
4545
+ compute_client.shelve_offload_server(server_id)
4448
4546
 
4449
4547
  if not parsed_args.wait:
4450
4548
  return
4451
4549
 
4452
- for server in parsed_args.servers:
4550
+ for server_id in server_ids:
4453
4551
  # We use osc-lib's wait_for_status since that allows for a callback
4454
4552
  # TODO(stephenfin): We should wait for these in parallel using e.g.
4455
4553
  # https://review.opendev.org/c/openstack/osc-lib/+/762503/
4456
4554
  if not utils.wait_for_status(
4457
4555
  compute_client.get_server,
4458
- server_obj.id,
4556
+ server_id,
4459
4557
  success_status=('shelved_offloaded',),
4460
4558
  callback=_show_progress,
4461
4559
  ):
4462
- LOG.error(
4463
- _('Error offloading shelved server %s'),
4464
- server_obj.id,
4465
- )
4466
- self.app.stdout.write(
4467
- _('Error offloading shelved server: %s\n') % server_obj.id
4468
- )
4469
- raise SystemExit
4560
+ msg = _('Error offloading shelved server: %s') % server_id
4561
+ raise exceptions.CommandError(msg)
4470
4562
 
4471
4563
 
4472
4564
  class ShowServer(command.ShowOne):
@@ -4928,8 +5020,8 @@ class UnsetServer(command.Command):
4928
5020
  if parsed_args.description:
4929
5021
  if compute_client.api_version < api_versions.APIVersion("2.19"):
4930
5022
  msg = _(
4931
- "Description is not supported for "
4932
- "--os-compute-api-version less than 2.19"
5023
+ '--os-compute-api-version 2.19 or greater is '
5024
+ 'required to support the --description option'
4933
5025
  )
4934
5026
  raise exceptions.CommandError(msg)
4935
5027
  compute_client.servers.update(
@@ -5058,8 +5150,5 @@ class UnshelveServer(command.Command):
5058
5150
  success_status=('active', 'shutoff'),
5059
5151
  callback=_show_progress,
5060
5152
  ):
5061
- LOG.error(_('Error unshelving server %s'), server_obj.id)
5062
- self.app.stdout.write(
5063
- _('Error unshelving server: %s\n') % server_obj.id
5064
- )
5065
- raise SystemExit
5153
+ msg = _('Error unshelving server: %s') % server_obj.id
5154
+ raise exceptions.CommandError(msg)