python-openstackclient 10.0.0__py3-none-any.whl → 10.1.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 (203) hide show
  1. openstackclient/common/availability_zone.py +1 -1
  2. openstackclient/common/module.py +21 -27
  3. openstackclient/common/pagination.py +42 -4
  4. openstackclient/common/project_cleanup.py +1 -2
  5. openstackclient/common/quota.py +9 -5
  6. openstackclient/compute/v2/flavor.py +3 -1
  7. openstackclient/compute/v2/hypervisor.py +2 -0
  8. openstackclient/compute/v2/keypair.py +6 -2
  9. openstackclient/compute/v2/server.py +21 -12
  10. openstackclient/compute/v2/server_event.py +8 -1
  11. openstackclient/compute/v2/server_group.py +2 -0
  12. openstackclient/compute/v2/server_migration.py +3 -0
  13. openstackclient/compute/v2/server_volume.py +3 -1
  14. openstackclient/compute/v2/service.py +3 -1
  15. openstackclient/compute/v2/usage.py +2 -2
  16. openstackclient/identity/common.py +5 -1
  17. openstackclient/identity/v3/access_rule.py +6 -0
  18. openstackclient/identity/v3/application_credential.py +10 -3
  19. openstackclient/identity/v3/credential.py +4 -2
  20. openstackclient/identity/v3/domain.py +4 -2
  21. openstackclient/identity/v3/endpoint.py +57 -45
  22. openstackclient/identity/v3/federation_protocol.py +7 -5
  23. openstackclient/identity/v3/group.py +11 -10
  24. openstackclient/identity/v3/identity_provider.py +4 -1
  25. openstackclient/identity/v3/limit.py +5 -2
  26. openstackclient/identity/v3/mapping.py +36 -19
  27. openstackclient/identity/v3/project.py +18 -5
  28. openstackclient/identity/v3/region.py +4 -2
  29. openstackclient/identity/v3/registered_limit.py +3 -2
  30. openstackclient/identity/v3/role.py +2 -1
  31. openstackclient/identity/v3/role_assignment.py +3 -2
  32. openstackclient/identity/v3/service.py +4 -2
  33. openstackclient/identity/v3/service_provider.py +4 -2
  34. openstackclient/identity/v3/trust.py +8 -5
  35. openstackclient/identity/v3/user.py +38 -11
  36. openstackclient/image/v2/cache.py +2 -2
  37. openstackclient/image/v2/image.py +15 -9
  38. openstackclient/image/v2/metadef_namespaces.py +11 -10
  39. openstackclient/image/v2/metadef_objects.py +5 -5
  40. openstackclient/image/v2/metadef_properties.py +7 -4
  41. openstackclient/image/v2/task.py +11 -22
  42. openstackclient/network/utils.py +0 -41
  43. openstackclient/network/v2/address_group.py +13 -1
  44. openstackclient/network/v2/address_scope.py +13 -8
  45. openstackclient/network/v2/bgpvpn/bgpvpn.py +33 -19
  46. openstackclient/network/v2/bgpvpn/network_association.py +25 -13
  47. openstackclient/network/v2/bgpvpn/port_association.py +35 -21
  48. openstackclient/network/v2/bgpvpn/router_association.py +27 -14
  49. openstackclient/network/v2/default_security_group_rule.py +14 -6
  50. openstackclient/network/v2/floating_ip.py +12 -4
  51. openstackclient/network/v2/floating_ip_port_forwarding.py +12 -2
  52. openstackclient/network/v2/fwaas/group.py +34 -1
  53. openstackclient/network/v2/fwaas/rule.py +39 -3
  54. openstackclient/network/v2/ip_availability.py +13 -4
  55. openstackclient/network/v2/l3_conntrack_helper.py +14 -1
  56. openstackclient/network/v2/local_ip.py +4 -1
  57. openstackclient/network/v2/local_ip_association.py +4 -1
  58. openstackclient/network/v2/ndp_proxy.py +4 -1
  59. openstackclient/network/v2/network.py +87 -20
  60. openstackclient/network/v2/network_agent.py +32 -10
  61. openstackclient/network/v2/network_auto_allocated_topology.py +6 -5
  62. openstackclient/network/v2/network_flavor.py +19 -6
  63. openstackclient/network/v2/network_flavor_profile.py +20 -6
  64. openstackclient/network/v2/network_meter.py +19 -6
  65. openstackclient/network/v2/network_meter_rule.py +20 -2
  66. openstackclient/network/v2/network_qos_policy.py +15 -7
  67. openstackclient/network/v2/network_qos_rule.py +16 -1
  68. openstackclient/network/v2/network_qos_rule_type.py +16 -5
  69. openstackclient/network/v2/network_rbac.py +12 -5
  70. openstackclient/network/v2/network_segment.py +13 -1
  71. openstackclient/network/v2/network_segment_range.py +15 -3
  72. openstackclient/network/v2/network_trunk.py +4 -1
  73. openstackclient/network/v2/port.py +88 -12
  74. openstackclient/network/v2/router.py +27 -16
  75. openstackclient/network/v2/security_group.py +18 -49
  76. openstackclient/network/v2/security_group_rule.py +18 -5
  77. openstackclient/network/v2/subnet.py +15 -7
  78. openstackclient/network/v2/subnet_pool.py +13 -8
  79. openstackclient/network/v2/taas/tap_flow.py +13 -3
  80. openstackclient/network/v2/taas/tap_mirror.py +7 -4
  81. openstackclient/network/v2/taas/tap_service.py +4 -1
  82. openstackclient/object/v1/container.py +3 -1
  83. openstackclient/object/v1/object.py +3 -1
  84. openstackclient/tests/functional/identity/v3/common.py +34 -0
  85. openstackclient/tests/functional/identity/v3/test_application_credential.py +1 -1
  86. openstackclient/tests/functional/identity/v3/test_mapping.py +81 -0
  87. openstackclient/tests/functional/volume/v3/test_volume_group.py +163 -0
  88. openstackclient/tests/unit/common/test_limits.py +1 -1
  89. openstackclient/tests/unit/common/test_module.py +77 -44
  90. openstackclient/tests/unit/common/test_quota.py +9 -0
  91. openstackclient/tests/unit/compute/v2/fakes.py +1 -57
  92. openstackclient/tests/unit/compute/v2/test_agent.py +4 -4
  93. openstackclient/tests/unit/compute/v2/test_aggregate.py +1 -1
  94. openstackclient/tests/unit/compute/v2/test_console.py +2 -2
  95. openstackclient/tests/unit/compute/v2/test_console_connection.py +1 -1
  96. openstackclient/tests/unit/compute/v2/test_flavor.py +1 -1
  97. openstackclient/tests/unit/compute/v2/test_host.py +3 -3
  98. openstackclient/tests/unit/compute/v2/test_hypervisor.py +2 -2
  99. openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py +1 -1
  100. openstackclient/tests/unit/compute/v2/test_keypair.py +1 -1
  101. openstackclient/tests/unit/compute/v2/test_server.py +15 -15
  102. openstackclient/tests/unit/compute/v2/test_server_backup.py +1 -1
  103. openstackclient/tests/unit/compute/v2/test_server_event.py +2 -2
  104. openstackclient/tests/unit/compute/v2/test_server_group.py +1 -1
  105. openstackclient/tests/unit/compute/v2/test_server_image.py +1 -1
  106. openstackclient/tests/unit/compute/v2/test_server_migration.py +4 -4
  107. openstackclient/tests/unit/compute/v2/test_server_share.py +4 -4
  108. openstackclient/tests/unit/compute/v2/test_server_volume.py +2 -2
  109. openstackclient/tests/unit/compute/v2/test_service.py +3 -3
  110. openstackclient/tests/unit/compute/v2/test_usage.py +1 -1
  111. openstackclient/tests/unit/identity/v2_0/fakes.py +3 -7
  112. openstackclient/tests/unit/identity/v2_0/test_endpoint.py +1 -1
  113. openstackclient/tests/unit/identity/v2_0/test_project.py +1 -1
  114. openstackclient/tests/unit/identity/v2_0/test_role.py +1 -1
  115. openstackclient/tests/unit/identity/v2_0/test_role_assignment.py +1 -1
  116. openstackclient/tests/unit/identity/v2_0/test_service.py +1 -1
  117. openstackclient/tests/unit/identity/v2_0/test_token.py +2 -2
  118. openstackclient/tests/unit/identity/v2_0/test_user.py +1 -1
  119. openstackclient/tests/unit/identity/v3/fakes.py +5 -38
  120. openstackclient/tests/unit/identity/v3/test_access_rule.py +3 -3
  121. openstackclient/tests/unit/identity/v3/test_application_credential.py +4 -4
  122. openstackclient/tests/unit/identity/v3/test_credential.py +5 -5
  123. openstackclient/tests/unit/identity/v3/test_domain.py +5 -5
  124. openstackclient/tests/unit/identity/v3/test_endpoint.py +6 -6
  125. openstackclient/tests/unit/identity/v3/test_endpoint_group.py +1 -1
  126. openstackclient/tests/unit/identity/v3/test_group.py +8 -8
  127. openstackclient/tests/unit/identity/v3/test_implied_role.py +1 -1
  128. openstackclient/tests/unit/identity/v3/test_limit.py +5 -5
  129. openstackclient/tests/unit/identity/v3/test_mappings.py +163 -79
  130. openstackclient/tests/unit/identity/v3/test_project.py +28 -5
  131. openstackclient/tests/unit/identity/v3/test_protocol.py +3 -3
  132. openstackclient/tests/unit/identity/v3/test_region.py +5 -5
  133. openstackclient/tests/unit/identity/v3/test_registered_limit.py +5 -5
  134. openstackclient/tests/unit/identity/v3/test_role.py +8 -8
  135. openstackclient/tests/unit/identity/v3/test_role_assignment.py +1 -1
  136. openstackclient/tests/unit/identity/v3/test_service.py +5 -5
  137. openstackclient/tests/unit/identity/v3/test_token.py +2 -2
  138. openstackclient/tests/unit/identity/v3/test_trust.py +4 -4
  139. openstackclient/tests/unit/identity/v3/test_user.py +73 -6
  140. openstackclient/tests/unit/network/v2/fakes.py +5 -77
  141. openstackclient/tests/unit/network/v2/fwaas/test_group.py +28 -2
  142. openstackclient/tests/unit/network/v2/fwaas/test_rule.py +28 -3
  143. openstackclient/tests/unit/network/v2/test_address_group.py +24 -0
  144. openstackclient/tests/unit/network/v2/test_address_scope.py +24 -0
  145. openstackclient/tests/unit/network/v2/test_floating_ip.py +24 -0
  146. openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py +24 -0
  147. openstackclient/tests/unit/network/v2/test_ip_availability.py +25 -0
  148. openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py +29 -3
  149. openstackclient/tests/unit/network/v2/test_network.py +74 -12
  150. openstackclient/tests/unit/network/v2/test_network_agent.py +50 -1
  151. openstackclient/tests/unit/network/v2/test_network_flavor.py +24 -0
  152. openstackclient/tests/unit/network/v2/test_network_flavor_profile.py +24 -0
  153. openstackclient/tests/unit/network/v2/test_network_meter.py +24 -0
  154. openstackclient/tests/unit/network/v2/test_network_qos_policy.py +24 -0
  155. openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py +24 -0
  156. openstackclient/tests/unit/network/v2/test_network_rbac.py +24 -0
  157. openstackclient/tests/unit/network/v2/test_network_segment.py +24 -0
  158. openstackclient/tests/unit/network/v2/test_network_segment_range.py +24 -0
  159. openstackclient/tests/unit/network/v2/test_port.py +166 -0
  160. openstackclient/tests/unit/network/v2/test_router.py +28 -7
  161. openstackclient/tests/unit/network/v2/test_security_group.py +22 -0
  162. openstackclient/tests/unit/network/v2/test_security_group_rule.py +25 -0
  163. openstackclient/tests/unit/network/v2/test_subnet.py +28 -4
  164. openstackclient/tests/unit/network/v2/test_subnet_pool.py +24 -0
  165. openstackclient/tests/unit/volume/v2/fakes.py +20 -140
  166. openstackclient/tests/unit/volume/v2/test_volume_backup.py +5 -9
  167. openstackclient/tests/unit/volume/v2/test_volume_snapshot.py +6 -0
  168. openstackclient/tests/unit/volume/v3/fakes.py +204 -100
  169. openstackclient/tests/unit/volume/v3/test_backup_record.py +114 -0
  170. openstackclient/tests/unit/volume/v3/test_consistency_group.py +720 -0
  171. openstackclient/tests/unit/volume/v3/test_consistency_group_snapshot.py +354 -0
  172. openstackclient/tests/unit/volume/v3/test_qos_specs.py +455 -0
  173. openstackclient/tests/unit/volume/v3/test_volume_attachment.py +2 -0
  174. openstackclient/tests/unit/volume/v3/test_volume_backend.py +158 -0
  175. openstackclient/tests/unit/volume/v3/test_volume_backup.py +5 -9
  176. openstackclient/tests/unit/volume/v3/test_volume_group_type.py +65 -0
  177. openstackclient/tests/unit/volume/v3/test_volume_host.py +115 -0
  178. openstackclient/tests/unit/volume/v3/test_volume_snapshot.py +6 -0
  179. openstackclient/volume/v2/volume.py +4 -2
  180. openstackclient/volume/v2/volume_backup.py +2 -3
  181. openstackclient/volume/v2/volume_snapshot.py +3 -4
  182. openstackclient/volume/v3/backup_record.py +94 -0
  183. openstackclient/volume/v3/consistency_group.py +400 -0
  184. openstackclient/volume/v3/consistency_group_snapshot.py +225 -0
  185. openstackclient/volume/v3/qos_specs.py +389 -0
  186. openstackclient/volume/v3/volume.py +4 -2
  187. openstackclient/volume/v3/volume_attachment.py +5 -1
  188. openstackclient/volume/v3/volume_backend.py +130 -0
  189. openstackclient/volume/v3/volume_backup.py +2 -3
  190. openstackclient/volume/v3/volume_group_snapshot.py +4 -6
  191. openstackclient/volume/v3/volume_group_type.py +1 -1
  192. openstackclient/volume/v3/volume_host.py +74 -0
  193. openstackclient/volume/v3/volume_message.py +3 -1
  194. openstackclient/volume/v3/volume_snapshot.py +2 -1
  195. {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/METADATA +3 -4
  196. {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/RECORD +202 -188
  197. {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/entry_points.txt +24 -24
  198. {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/licenses/AUTHORS +5 -0
  199. python_openstackclient-10.1.0.dist-info/pbr.json +1 -0
  200. python_openstackclient-10.0.0.dist-info/pbr.json +0 -1
  201. {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/WHEEL +0 -0
  202. {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/licenses/LICENSE +0 -0
  203. {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,400 @@
1
+ #
2
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
3
+ # not use this file except in compliance with the License. You may obtain
4
+ # a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
+ # License for the specific language governing permissions and limitations
12
+ # under the License.
13
+
14
+ """Volume v3 consistency group action implementations"""
15
+
16
+ import argparse
17
+ import logging
18
+ from collections.abc import Iterable, Sequence
19
+ from typing import Any
20
+
21
+ from osc_lib.cli import format_columns
22
+ from osc_lib import exceptions
23
+ from osc_lib import utils
24
+
25
+ from openstackclient import command
26
+ from openstackclient.i18n import _
27
+
28
+
29
+ LOG = logging.getLogger(__name__)
30
+
31
+
32
+ def _find_volumes(
33
+ parsed_args_volumes: list[str], volume_client: Any
34
+ ) -> tuple[int, str]:
35
+ result = 0
36
+ uuid = ''
37
+ for volume in parsed_args_volumes:
38
+ try:
39
+ volume_id = utils.find_resource(volume_client.volumes, volume).id
40
+ uuid += volume_id + ','
41
+ except Exception as e:
42
+ result += 1
43
+ LOG.error(
44
+ _("Failed to find volume with name or ID '%(volume)s':%(e)s"),
45
+ {'volume': volume, 'e': e},
46
+ )
47
+
48
+ return result, uuid
49
+
50
+
51
+ class AddVolumeToConsistencyGroup(command.Command):
52
+ _description = _("Add volume(s) to consistency group")
53
+
54
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
55
+ parser = super().get_parser(prog_name)
56
+ parser.add_argument(
57
+ 'consistency_group',
58
+ metavar="<consistency-group>",
59
+ help=_('Consistency group to contain <volume> (name or ID)'),
60
+ )
61
+ parser.add_argument(
62
+ 'volumes',
63
+ metavar='<volume>',
64
+ nargs='+',
65
+ help=_(
66
+ 'Volume(s) to add to <consistency-group> (name or ID) '
67
+ '(repeat option to add multiple volumes)'
68
+ ),
69
+ )
70
+ return parser
71
+
72
+ def take_action(self, parsed_args: argparse.Namespace) -> None:
73
+ volume_client = self.app.client_manager.volume
74
+ result, add_uuid = _find_volumes(parsed_args.volumes, volume_client)
75
+
76
+ if result > 0:
77
+ total = len(parsed_args.volumes)
78
+ LOG.error(
79
+ _("%(result)s of %(total)s volumes failed to add."),
80
+ {'result': result, 'total': total},
81
+ )
82
+
83
+ if add_uuid:
84
+ add_uuid = add_uuid.rstrip(',')
85
+ consistency_group_id = utils.find_resource(
86
+ volume_client.consistencygroups, parsed_args.consistency_group
87
+ ).id
88
+ volume_client.consistencygroups.update(
89
+ consistency_group_id, add_volumes=add_uuid
90
+ )
91
+
92
+
93
+ class CreateConsistencyGroup(command.ShowOne):
94
+ _description = _("Create new consistency group.")
95
+
96
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
97
+ parser = super().get_parser(prog_name)
98
+ parser.add_argument(
99
+ "name",
100
+ metavar="<name>",
101
+ nargs="?",
102
+ help=_("Name of new consistency group (default to None)"),
103
+ )
104
+ exclusive_group = parser.add_mutually_exclusive_group(required=True)
105
+ exclusive_group.add_argument(
106
+ "--volume-type",
107
+ metavar="<volume-type>",
108
+ help=_("Volume type of this consistency group (name or ID)"),
109
+ )
110
+ exclusive_group.add_argument(
111
+ "--source",
112
+ metavar="<consistency-group>",
113
+ help=_("Existing consistency group (name or ID)"),
114
+ )
115
+ # NOTE(stephenfin): Legacy alias
116
+ exclusive_group.add_argument(
117
+ "--consistency-group-source",
118
+ metavar="<consistency-group>",
119
+ dest='source',
120
+ help=argparse.SUPPRESS,
121
+ )
122
+ exclusive_group.add_argument(
123
+ "--snapshot",
124
+ metavar="<consistency-group-snapshot>",
125
+ help=_("Existing consistency group snapshot (name or ID)"),
126
+ )
127
+ # NOTE(stephenfin): Legacy alias
128
+ exclusive_group.add_argument(
129
+ "--consistency-group-snapshot",
130
+ metavar="<consistency-group-snapshot>",
131
+ dest='snapshot',
132
+ help=argparse.SUPPRESS,
133
+ )
134
+ parser.add_argument(
135
+ "--description",
136
+ metavar="<description>",
137
+ help=_("Description of this consistency group"),
138
+ )
139
+ parser.add_argument(
140
+ "--availability-zone",
141
+ metavar="<availability-zone>",
142
+ help=_(
143
+ "Availability zone for this consistency group "
144
+ "(not available if creating consistency group "
145
+ "from source)"
146
+ ),
147
+ )
148
+ return parser
149
+
150
+ def take_action(
151
+ self, parsed_args: argparse.Namespace
152
+ ) -> tuple[Sequence[str], Iterable[Any]]:
153
+ volume_client = self.app.client_manager.volume
154
+ if parsed_args.volume_type:
155
+ volume_type_id = utils.find_resource(
156
+ volume_client.volume_types, parsed_args.volume_type
157
+ ).id
158
+ consistency_group = volume_client.consistencygroups.create(
159
+ volume_type_id,
160
+ name=parsed_args.name,
161
+ description=parsed_args.description,
162
+ availability_zone=parsed_args.availability_zone,
163
+ )
164
+ else:
165
+ if parsed_args.availability_zone:
166
+ msg = _(
167
+ "'--availability-zone' option will not work "
168
+ "if creating consistency group from source"
169
+ )
170
+ LOG.warning(msg)
171
+
172
+ consistency_group_id = None
173
+ consistency_group_snapshot = None
174
+ if parsed_args.source:
175
+ consistency_group_id = utils.find_resource(
176
+ volume_client.consistencygroups,
177
+ parsed_args.source,
178
+ ).id
179
+ elif parsed_args.snapshot:
180
+ consistency_group_snapshot = utils.find_resource(
181
+ volume_client.cgsnapshots,
182
+ parsed_args.snapshot,
183
+ ).id
184
+
185
+ consistency_group = (
186
+ volume_client.consistencygroups.create_from_src(
187
+ consistency_group_snapshot,
188
+ consistency_group_id,
189
+ name=parsed_args.name,
190
+ description=parsed_args.description,
191
+ )
192
+ )
193
+
194
+ col_headers, col_data = zip(*sorted(consistency_group._info.items()))
195
+ return col_headers, col_data
196
+
197
+
198
+ class DeleteConsistencyGroup(command.Command):
199
+ _description = _("Delete consistency group(s).")
200
+
201
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
202
+ parser = super().get_parser(prog_name)
203
+ parser.add_argument(
204
+ 'consistency_groups',
205
+ metavar='<consistency-group>',
206
+ nargs="+",
207
+ help=_('Consistency group(s) to delete (name or ID)'),
208
+ )
209
+ parser.add_argument(
210
+ '--force',
211
+ action='store_true',
212
+ default=False,
213
+ help=_("Allow delete in state other than error or available"),
214
+ )
215
+ return parser
216
+
217
+ def take_action(self, parsed_args: argparse.Namespace) -> None:
218
+ volume_client = self.app.client_manager.volume
219
+ result = 0
220
+
221
+ for i in parsed_args.consistency_groups:
222
+ try:
223
+ consistency_group_id = utils.find_resource(
224
+ volume_client.consistencygroups, i
225
+ ).id
226
+ volume_client.consistencygroups.delete(
227
+ consistency_group_id, parsed_args.force
228
+ )
229
+ except Exception as e:
230
+ result += 1
231
+ LOG.error(
232
+ _(
233
+ "Failed to delete consistency group with "
234
+ "name or ID '%(consistency_group)s':%(e)s"
235
+ ),
236
+ {'consistency_group': i, 'e': e},
237
+ )
238
+
239
+ if result > 0:
240
+ total = len(parsed_args.consistency_groups)
241
+ msg = _(
242
+ "%(result)s of %(total)s consistency groups failed to delete."
243
+ ) % {'result': result, 'total': total}
244
+ raise exceptions.CommandError(msg)
245
+
246
+
247
+ class ListConsistencyGroup(command.Lister):
248
+ _description = _("List consistency groups.")
249
+
250
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
251
+ parser = super().get_parser(prog_name)
252
+ parser.add_argument(
253
+ '--all-projects',
254
+ action="store_true",
255
+ help=_(
256
+ 'Show details for all projects. Admin only. '
257
+ '(defaults to False)'
258
+ ),
259
+ )
260
+ parser.add_argument(
261
+ '--long',
262
+ action="store_true",
263
+ help=_('List additional fields in output'),
264
+ )
265
+ return parser
266
+
267
+ def take_action(
268
+ self, parsed_args: argparse.Namespace
269
+ ) -> tuple[Sequence[str], Iterable[tuple[Any, ...]]]:
270
+ if parsed_args.long:
271
+ columns = [
272
+ 'ID',
273
+ 'Status',
274
+ 'Availability Zone',
275
+ 'Name',
276
+ 'Description',
277
+ 'Volume Types',
278
+ ]
279
+ else:
280
+ columns = ['ID', 'Status', 'Name']
281
+ volume_client = self.app.client_manager.volume
282
+ consistency_groups = volume_client.consistencygroups.list(
283
+ detailed=True,
284
+ search_opts={'all_tenants': parsed_args.all_projects},
285
+ )
286
+
287
+ return (
288
+ columns,
289
+ (
290
+ utils.get_item_properties(
291
+ s,
292
+ columns,
293
+ formatters={'Volume Types': format_columns.ListColumn},
294
+ )
295
+ for s in consistency_groups
296
+ ),
297
+ )
298
+
299
+
300
+ class RemoveVolumeFromConsistencyGroup(command.Command):
301
+ _description = _("Remove volume(s) from consistency group")
302
+
303
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
304
+ parser = super().get_parser(prog_name)
305
+ parser.add_argument(
306
+ 'consistency_group',
307
+ metavar="<consistency-group>",
308
+ help=_('Consistency group containing <volume> (name or ID)'),
309
+ )
310
+ parser.add_argument(
311
+ 'volumes',
312
+ metavar='<volume>',
313
+ nargs='+',
314
+ help=_(
315
+ 'Volume(s) to remove from <consistency-group> (name or ID) '
316
+ '(repeat option to remove multiple volumes)'
317
+ ),
318
+ )
319
+ return parser
320
+
321
+ def take_action(self, parsed_args: argparse.Namespace) -> None:
322
+ volume_client = self.app.client_manager.volume
323
+ result, remove_uuid = _find_volumes(parsed_args.volumes, volume_client)
324
+
325
+ if result > 0:
326
+ total = len(parsed_args.volumes)
327
+ LOG.error(
328
+ _("%(result)s of %(total)s volumes failed to remove."),
329
+ {'result': result, 'total': total},
330
+ )
331
+
332
+ if remove_uuid:
333
+ remove_uuid = remove_uuid.rstrip(',')
334
+ consistency_group_id = utils.find_resource(
335
+ volume_client.consistencygroups, parsed_args.consistency_group
336
+ ).id
337
+ volume_client.consistencygroups.update(
338
+ consistency_group_id, remove_volumes=remove_uuid
339
+ )
340
+
341
+
342
+ class SetConsistencyGroup(command.Command):
343
+ _description = _("Set consistency group properties")
344
+
345
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
346
+ parser = super().get_parser(prog_name)
347
+ parser.add_argument(
348
+ 'consistency_group',
349
+ metavar='<consistency-group>',
350
+ help=_('Consistency group to modify (name or ID)'),
351
+ )
352
+ parser.add_argument(
353
+ '--name',
354
+ metavar='<name>',
355
+ help=_('New consistency group name'),
356
+ )
357
+ parser.add_argument(
358
+ '--description',
359
+ metavar='<description>',
360
+ help=_('New consistency group description'),
361
+ )
362
+ return parser
363
+
364
+ def take_action(self, parsed_args: argparse.Namespace) -> None:
365
+ volume_client = self.app.client_manager.volume
366
+ kwargs = {}
367
+ if parsed_args.name:
368
+ kwargs['name'] = parsed_args.name
369
+ if parsed_args.description:
370
+ kwargs['description'] = parsed_args.description
371
+ if kwargs:
372
+ consistency_group_id = utils.find_resource(
373
+ volume_client.consistencygroups, parsed_args.consistency_group
374
+ ).id
375
+ volume_client.consistencygroups.update(
376
+ consistency_group_id, **kwargs
377
+ )
378
+
379
+
380
+ class ShowConsistencyGroup(command.ShowOne):
381
+ _description = _("Display consistency group details.")
382
+
383
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
384
+ parser = super().get_parser(prog_name)
385
+ parser.add_argument(
386
+ "consistency_group",
387
+ metavar="<consistency-group>",
388
+ help=_("Consistency group to display (name or ID)"),
389
+ )
390
+ return parser
391
+
392
+ def take_action(
393
+ self, parsed_args: argparse.Namespace
394
+ ) -> tuple[Sequence[str], Iterable[Any]]:
395
+ volume_client = self.app.client_manager.volume
396
+ consistency_group = utils.find_resource(
397
+ volume_client.consistencygroups, parsed_args.consistency_group
398
+ )
399
+ col_headers, col_data = zip(*sorted(consistency_group._info.items()))
400
+ return col_headers, col_data
@@ -0,0 +1,225 @@
1
+ #
2
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
3
+ # not use this file except in compliance with the License. You may obtain
4
+ # a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
+ # License for the specific language governing permissions and limitations
12
+ # under the License.
13
+
14
+ """Volume v3 consistency group snapshot action implementations"""
15
+
16
+ import argparse
17
+ import logging
18
+ from collections.abc import Iterable, Sequence
19
+ from typing import Any
20
+
21
+ from osc_lib import exceptions
22
+ from osc_lib import utils
23
+
24
+ from openstackclient import command
25
+ from openstackclient.i18n import _
26
+
27
+
28
+ LOG = logging.getLogger(__name__)
29
+
30
+
31
+ class CreateConsistencyGroupSnapshot(command.ShowOne):
32
+ _description = _("Create new consistency group snapshot.")
33
+
34
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
35
+ parser = super().get_parser(prog_name)
36
+ parser.add_argument(
37
+ "snapshot_name",
38
+ metavar="<snapshot-name>",
39
+ nargs="?",
40
+ help=_("Name of new consistency group snapshot (default to None)"),
41
+ )
42
+ parser.add_argument(
43
+ "--consistency-group",
44
+ metavar="<consistency-group>",
45
+ help=_(
46
+ "Consistency group to snapshot (name or ID) "
47
+ "(default to be the same as <snapshot-name>)"
48
+ ),
49
+ )
50
+ parser.add_argument(
51
+ "--description",
52
+ metavar="<description>",
53
+ help=_("Description of this consistency group snapshot"),
54
+ )
55
+ return parser
56
+
57
+ def take_action(
58
+ self, parsed_args: argparse.Namespace
59
+ ) -> tuple[Sequence[str], Iterable[Any]]:
60
+ volume_client = self.app.client_manager.volume
61
+ consistency_group = parsed_args.consistency_group
62
+ if not parsed_args.consistency_group:
63
+ # If "--consistency-group" not specified, then consistency_group
64
+ # will be the same as the new consistency group snapshot name
65
+ consistency_group = parsed_args.snapshot_name
66
+ consistency_group_id = utils.find_resource(
67
+ volume_client.consistencygroups, consistency_group
68
+ ).id
69
+ consistency_group_snapshot = volume_client.cgsnapshots.create(
70
+ consistency_group_id,
71
+ name=parsed_args.snapshot_name,
72
+ description=parsed_args.description,
73
+ )
74
+
75
+ col_headers, col_data = zip(
76
+ *sorted(consistency_group_snapshot._info.items())
77
+ )
78
+ return col_headers, col_data
79
+
80
+
81
+ class DeleteConsistencyGroupSnapshot(command.Command):
82
+ _description = _("Delete consistency group snapshot(s).")
83
+
84
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
85
+ parser = super().get_parser(prog_name)
86
+ parser.add_argument(
87
+ "consistency_group_snapshot",
88
+ metavar="<consistency-group-snapshot>",
89
+ nargs="+",
90
+ help=_("Consistency group snapshot(s) to delete (name or ID)"),
91
+ )
92
+ return parser
93
+
94
+ def take_action(self, parsed_args: argparse.Namespace) -> None:
95
+ volume_client = self.app.client_manager.volume
96
+ result = 0
97
+
98
+ for snapshot in parsed_args.consistency_group_snapshot:
99
+ try:
100
+ snapshot_id = utils.find_resource(
101
+ volume_client.cgsnapshots, snapshot
102
+ ).id
103
+
104
+ volume_client.cgsnapshots.delete(snapshot_id)
105
+ except Exception as e:
106
+ result += 1
107
+ LOG.error(
108
+ _(
109
+ "Failed to delete consistency group snapshot "
110
+ "with name or ID '%(snapshot)s': %(e)s"
111
+ ),
112
+ {'snapshot': snapshot, 'e': e},
113
+ )
114
+
115
+ if result > 0:
116
+ total = len(parsed_args.consistency_group_snapshot)
117
+ msg = _(
118
+ "%(result)s of %(total)s consistency group snapshots "
119
+ "failed to delete."
120
+ ) % {'result': result, 'total': total}
121
+ raise exceptions.CommandError(msg)
122
+
123
+
124
+ class ListConsistencyGroupSnapshot(command.Lister):
125
+ _description = _("List consistency group snapshots.")
126
+
127
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
128
+ parser = super().get_parser(prog_name)
129
+ parser.add_argument(
130
+ '--all-projects',
131
+ action="store_true",
132
+ help=_(
133
+ 'Show detail for all projects (admin only) (defaults to False)'
134
+ ),
135
+ )
136
+ parser.add_argument(
137
+ '--long',
138
+ action="store_true",
139
+ help=_('List additional fields in output'),
140
+ )
141
+ parser.add_argument(
142
+ '--status',
143
+ metavar="<status>",
144
+ choices=[
145
+ 'available',
146
+ 'error',
147
+ 'creating',
148
+ 'deleting',
149
+ 'error_deleting',
150
+ ],
151
+ help=_(
152
+ 'Filters results by a status ("available", "error", '
153
+ '"creating", "deleting" or "error_deleting")'
154
+ ),
155
+ )
156
+ parser.add_argument(
157
+ '--consistency-group',
158
+ metavar="<consistency-group>",
159
+ help=_('Filters results by a consistency group (name or ID)'),
160
+ )
161
+ return parser
162
+
163
+ def take_action(
164
+ self, parsed_args: argparse.Namespace
165
+ ) -> tuple[Sequence[str], Iterable[tuple[Any, ...]]]:
166
+ if parsed_args.long:
167
+ columns = [
168
+ 'ID',
169
+ 'Status',
170
+ 'ConsistencyGroup ID',
171
+ 'Name',
172
+ 'Description',
173
+ 'Created At',
174
+ ]
175
+ else:
176
+ columns = ['ID', 'Status', 'Name']
177
+ volume_client = self.app.client_manager.volume
178
+ consistency_group_id = None
179
+ if parsed_args.consistency_group:
180
+ consistency_group_id = utils.find_resource(
181
+ volume_client.consistencygroups,
182
+ parsed_args.consistency_group,
183
+ ).id
184
+ search_opts = {
185
+ 'all_tenants': parsed_args.all_projects,
186
+ 'status': parsed_args.status,
187
+ 'consistencygroup_id': consistency_group_id,
188
+ }
189
+ consistency_group_snapshots = volume_client.cgsnapshots.list(
190
+ detailed=True,
191
+ search_opts=search_opts,
192
+ )
193
+
194
+ return (
195
+ columns,
196
+ (
197
+ utils.get_item_properties(s, columns)
198
+ for s in consistency_group_snapshots
199
+ ),
200
+ )
201
+
202
+
203
+ class ShowConsistencyGroupSnapshot(command.ShowOne):
204
+ _description = _("Display consistency group snapshot details")
205
+
206
+ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
207
+ parser = super().get_parser(prog_name)
208
+ parser.add_argument(
209
+ "consistency_group_snapshot",
210
+ metavar="<consistency-group-snapshot>",
211
+ help=_("Consistency group snapshot to display (name or ID)"),
212
+ )
213
+ return parser
214
+
215
+ def take_action(
216
+ self, parsed_args: argparse.Namespace
217
+ ) -> tuple[Sequence[str], Iterable[Any]]:
218
+ volume_client = self.app.client_manager.volume
219
+ consistency_group_snapshot = utils.find_resource(
220
+ volume_client.cgsnapshots, parsed_args.consistency_group_snapshot
221
+ )
222
+ col_headers, col_data = zip(
223
+ *sorted(consistency_group_snapshot._info.items())
224
+ )
225
+ return col_headers, col_data