python-openstackclient 6.6.0__py3-none-any.whl → 7.0.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 (337) hide show
  1. openstackclient/api/api.py +7 -8
  2. openstackclient/api/compute_v2.py +352 -638
  3. openstackclient/api/image_v1.py +1 -1
  4. openstackclient/api/object_store_v1.py +3 -4
  5. openstackclient/common/availability_zone.py +1 -1
  6. openstackclient/common/clientmanager.py +16 -4
  7. openstackclient/common/configuration.py +1 -1
  8. openstackclient/common/extension.py +1 -1
  9. openstackclient/common/limits.py +66 -32
  10. openstackclient/common/module.py +3 -3
  11. openstackclient/common/progressbar.py +2 -2
  12. openstackclient/common/project_cleanup.py +5 -2
  13. openstackclient/common/quota.py +281 -410
  14. openstackclient/common/versions.py +1 -1
  15. openstackclient/compute/client.py +7 -116
  16. openstackclient/compute/v2/agent.py +75 -49
  17. openstackclient/compute/v2/aggregate.py +9 -9
  18. openstackclient/compute/v2/console.py +2 -2
  19. openstackclient/compute/v2/flavor.py +6 -6
  20. openstackclient/compute/v2/host.py +38 -33
  21. openstackclient/compute/v2/hypervisor.py +4 -3
  22. openstackclient/compute/v2/keypair.py +7 -8
  23. openstackclient/compute/v2/server.py +478 -396
  24. openstackclient/compute/v2/server_backup.py +1 -1
  25. openstackclient/compute/v2/server_group.py +4 -4
  26. openstackclient/compute/v2/server_image.py +1 -1
  27. openstackclient/compute/v2/server_migration.py +3 -4
  28. openstackclient/compute/v2/service.py +4 -4
  29. openstackclient/compute/v2/usage.py +3 -3
  30. openstackclient/identity/common.py +34 -0
  31. openstackclient/identity/v2_0/catalog.py +2 -2
  32. openstackclient/identity/v2_0/ec2creds.py +4 -4
  33. openstackclient/identity/v2_0/endpoint.py +4 -4
  34. openstackclient/identity/v2_0/project.py +6 -6
  35. openstackclient/identity/v2_0/role.py +5 -5
  36. openstackclient/identity/v2_0/role_assignment.py +1 -1
  37. openstackclient/identity/v2_0/service.py +4 -4
  38. openstackclient/identity/v2_0/token.py +2 -2
  39. openstackclient/identity/v2_0/user.py +7 -7
  40. openstackclient/identity/v3/access_rule.py +3 -3
  41. openstackclient/identity/v3/application_credential.py +127 -45
  42. openstackclient/identity/v3/catalog.py +2 -2
  43. openstackclient/identity/v3/consumer.py +4 -4
  44. openstackclient/identity/v3/credential.py +5 -5
  45. openstackclient/identity/v3/domain.py +5 -5
  46. openstackclient/identity/v3/ec2creds.py +4 -4
  47. openstackclient/identity/v3/endpoint.py +7 -7
  48. openstackclient/identity/v3/endpoint_group.py +8 -10
  49. openstackclient/identity/v3/federation_protocol.py +5 -5
  50. openstackclient/identity/v3/group.py +8 -8
  51. openstackclient/identity/v3/identity_provider.py +5 -5
  52. openstackclient/identity/v3/implied_role.py +3 -3
  53. openstackclient/identity/v3/limit.py +5 -5
  54. openstackclient/identity/v3/mapping.py +5 -5
  55. openstackclient/identity/v3/policy.py +5 -5
  56. openstackclient/identity/v3/project.py +5 -5
  57. openstackclient/identity/v3/region.py +5 -5
  58. openstackclient/identity/v3/registered_limit.py +5 -5
  59. openstackclient/identity/v3/role.py +7 -7
  60. openstackclient/identity/v3/role_assignment.py +92 -140
  61. openstackclient/identity/v3/service.py +64 -34
  62. openstackclient/identity/v3/service_provider.py +4 -4
  63. openstackclient/identity/v3/tag.py +2 -2
  64. openstackclient/identity/v3/token.py +5 -5
  65. openstackclient/identity/v3/trust.py +3 -3
  66. openstackclient/identity/v3/user.py +144 -80
  67. openstackclient/image/client.py +4 -4
  68. openstackclient/image/v1/image.py +8 -9
  69. openstackclient/image/v2/cache.py +12 -10
  70. openstackclient/image/v2/metadef_objects.py +44 -0
  71. openstackclient/image/v2/metadef_resource_type_association.py +189 -0
  72. openstackclient/image/v2/task.py +1 -1
  73. openstackclient/network/common.py +6 -5
  74. openstackclient/network/utils.py +2 -2
  75. openstackclient/network/v2/address_group.py +6 -6
  76. openstackclient/network/v2/address_scope.py +5 -5
  77. openstackclient/network/v2/default_security_group_rule.py +1 -1
  78. openstackclient/network/v2/floating_ip.py +8 -10
  79. openstackclient/network/v2/floating_ip_pool.py +6 -15
  80. openstackclient/network/v2/floating_ip_port_forwarding.py +5 -13
  81. openstackclient/network/v2/ip_availability.py +2 -2
  82. openstackclient/network/v2/l3_conntrack_helper.py +5 -5
  83. openstackclient/network/v2/network.py +8 -8
  84. openstackclient/network/v2/network_agent.py +8 -8
  85. openstackclient/network/v2/network_auto_allocated_topology.py +2 -2
  86. openstackclient/network/v2/network_flavor.py +6 -8
  87. openstackclient/network/v2/network_flavor_profile.py +4 -4
  88. openstackclient/network/v2/network_meter.py +3 -3
  89. openstackclient/network/v2/network_meter_rule.py +3 -3
  90. openstackclient/network/v2/network_qos_policy.py +5 -5
  91. openstackclient/network/v2/network_qos_rule.py +9 -9
  92. openstackclient/network/v2/network_qos_rule_type.py +1 -1
  93. openstackclient/network/v2/network_rbac.py +5 -5
  94. openstackclient/network/v2/network_segment.py +5 -5
  95. openstackclient/network/v2/network_segment_range.py +7 -7
  96. openstackclient/network/v2/network_trunk.py +7 -7
  97. openstackclient/network/v2/port.py +26 -12
  98. openstackclient/network/v2/router.py +403 -54
  99. openstackclient/network/v2/security_group.py +18 -14
  100. openstackclient/network/v2/security_group_rule.py +18 -15
  101. openstackclient/network/v2/subnet.py +15 -8
  102. openstackclient/network/v2/subnet_pool.py +6 -6
  103. openstackclient/object/v1/account.py +2 -2
  104. openstackclient/object/v1/container.py +7 -7
  105. openstackclient/object/v1/object.py +7 -7
  106. openstackclient/shell.py +4 -6
  107. openstackclient/tests/functional/base.py +1 -1
  108. openstackclient/tests/functional/common/test_extension.py +1 -1
  109. openstackclient/tests/functional/common/test_help.py +2 -2
  110. openstackclient/tests/functional/common/test_module.py +1 -1
  111. openstackclient/tests/functional/common/test_quota.py +43 -61
  112. openstackclient/tests/functional/compute/v2/common.py +2 -2
  113. openstackclient/tests/functional/compute/v2/test_flavor.py +2 -2
  114. openstackclient/tests/functional/compute/v2/test_keypair.py +1 -1
  115. openstackclient/tests/functional/compute/v2/test_server.py +5 -5
  116. openstackclient/tests/functional/compute/v2/test_server_event.py +1 -1
  117. openstackclient/tests/functional/identity/v2/common.py +3 -3
  118. openstackclient/tests/functional/identity/v3/common.py +14 -6
  119. openstackclient/tests/functional/identity/v3/test_application_credential.py +13 -19
  120. openstackclient/tests/functional/identity/v3/test_domain.py +1 -3
  121. openstackclient/tests/functional/identity/v3/test_endpoint.py +1 -1
  122. openstackclient/tests/functional/identity/v3/test_idp.py +1 -1
  123. openstackclient/tests/functional/identity/v3/test_limit.py +2 -2
  124. openstackclient/tests/functional/identity/v3/test_region.py +1 -3
  125. openstackclient/tests/functional/identity/v3/test_registered_limit.py +1 -1
  126. openstackclient/tests/functional/identity/v3/test_role.py +2 -2
  127. openstackclient/tests/functional/identity/v3/test_role_assignment.py +210 -0
  128. openstackclient/tests/functional/identity/v3/test_service.py +4 -6
  129. openstackclient/tests/functional/identity/v3/test_service_provider.py +1 -3
  130. openstackclient/tests/functional/image/base.py +1 -1
  131. openstackclient/tests/functional/image/v2/test_image.py +1 -1
  132. openstackclient/tests/functional/image/v2/test_info.py +1 -1
  133. openstackclient/tests/functional/network/v2/common.py +4 -6
  134. openstackclient/tests/functional/network/v2/test_network.py +5 -3
  135. openstackclient/tests/functional/network/v2/test_network_agent.py +7 -5
  136. openstackclient/tests/functional/network/v2/test_network_qos_rule.py +4 -4
  137. openstackclient/tests/functional/network/v2/test_port.py +11 -7
  138. openstackclient/tests/functional/network/v2/test_router.py +2 -2
  139. openstackclient/tests/functional/object/v1/common.py +1 -1
  140. openstackclient/tests/functional/object/v1/test_container.py +3 -3
  141. openstackclient/tests/functional/object/v1/test_object.py +9 -13
  142. openstackclient/tests/functional/volume/base.py +1 -1
  143. openstackclient/tests/functional/volume/v1/test_service.py +1 -1
  144. openstackclient/tests/functional/volume/v1/test_snapshot.py +2 -2
  145. openstackclient/tests/functional/volume/v1/test_transfer_request.py +2 -2
  146. openstackclient/tests/functional/volume/v1/test_volume_type.py +1 -1
  147. openstackclient/tests/functional/volume/v2/test_service.py +2 -2
  148. openstackclient/tests/functional/volume/v2/test_volume_backup.py +2 -2
  149. openstackclient/tests/functional/volume/v2/test_volume_snapshot.py +2 -2
  150. openstackclient/tests/functional/volume/v2/test_volume_type.py +1 -1
  151. openstackclient/tests/functional/volume/v3/test_volume_snapshot.py +2 -2
  152. openstackclient/tests/functional/volume/v3/test_volume_type.py +1 -1
  153. openstackclient/tests/unit/api/fakes.py +1 -1
  154. openstackclient/tests/unit/api/test_api.py +2 -2
  155. openstackclient/tests/unit/api/test_compute_v2.py +522 -707
  156. openstackclient/tests/unit/api/test_image_v1.py +1 -1
  157. openstackclient/tests/unit/api/test_image_v2.py +1 -1
  158. openstackclient/tests/unit/api/test_object_store_v1.py +4 -4
  159. openstackclient/tests/unit/common/test_limits.py +73 -35
  160. openstackclient/tests/unit/common/test_logs.py +2 -2
  161. openstackclient/tests/unit/common/test_module.py +4 -2
  162. openstackclient/tests/unit/common/test_project_cleanup.py +31 -6
  163. openstackclient/tests/unit/common/test_quota.py +490 -630
  164. openstackclient/tests/unit/compute/v2/fakes.py +37 -286
  165. openstackclient/tests/unit/compute/v2/test_agent.py +189 -147
  166. openstackclient/tests/unit/compute/v2/test_aggregate.py +18 -16
  167. openstackclient/tests/unit/compute/v2/test_console.py +4 -5
  168. openstackclient/tests/unit/compute/v2/test_flavor.py +59 -68
  169. openstackclient/tests/unit/compute/v2/test_host.py +83 -54
  170. openstackclient/tests/unit/compute/v2/test_hypervisor.py +28 -31
  171. openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py +2 -2
  172. openstackclient/tests/unit/compute/v2/test_keypair.py +65 -50
  173. openstackclient/tests/unit/compute/v2/test_server.py +2895 -2459
  174. openstackclient/tests/unit/compute/v2/test_server_backup.py +1 -1
  175. openstackclient/tests/unit/compute/v2/test_server_event.py +14 -39
  176. openstackclient/tests/unit/compute/v2/test_server_group.py +28 -29
  177. openstackclient/tests/unit/compute/v2/test_server_migration.py +43 -68
  178. openstackclient/tests/unit/compute/v2/test_server_volume.py +17 -34
  179. openstackclient/tests/unit/compute/v2/test_service.py +34 -52
  180. openstackclient/tests/unit/compute/v2/test_usage.py +4 -4
  181. openstackclient/tests/unit/fakes.py +11 -11
  182. openstackclient/tests/unit/identity/v2_0/fakes.py +27 -10
  183. openstackclient/tests/unit/identity/v2_0/test_catalog.py +3 -3
  184. openstackclient/tests/unit/identity/v2_0/test_endpoint.py +7 -7
  185. openstackclient/tests/unit/identity/v2_0/test_project.py +8 -8
  186. openstackclient/tests/unit/identity/v2_0/test_role.py +10 -10
  187. openstackclient/tests/unit/identity/v2_0/test_role_assignment.py +4 -4
  188. openstackclient/tests/unit/identity/v2_0/test_service.py +6 -6
  189. openstackclient/tests/unit/identity/v2_0/test_token.py +4 -4
  190. openstackclient/tests/unit/identity/v2_0/test_user.py +8 -8
  191. openstackclient/tests/unit/identity/v3/fakes.py +59 -20
  192. openstackclient/tests/unit/identity/v3/test_access_rule.py +5 -5
  193. openstackclient/tests/unit/identity/v3/test_application_credential.py +212 -235
  194. openstackclient/tests/unit/identity/v3/test_catalog.py +3 -3
  195. openstackclient/tests/unit/identity/v3/test_consumer.py +7 -8
  196. openstackclient/tests/unit/identity/v3/test_credential.py +9 -9
  197. openstackclient/tests/unit/identity/v3/test_domain.py +8 -8
  198. openstackclient/tests/unit/identity/v3/test_endpoint.py +13 -13
  199. openstackclient/tests/unit/identity/v3/test_endpoint_group.py +12 -14
  200. openstackclient/tests/unit/identity/v3/test_group.py +12 -12
  201. openstackclient/tests/unit/identity/v3/test_identity_provider.py +8 -8
  202. openstackclient/tests/unit/identity/v3/test_implied_role.py +5 -5
  203. openstackclient/tests/unit/identity/v3/test_limit.py +7 -7
  204. openstackclient/tests/unit/identity/v3/test_mappings.py +7 -7
  205. openstackclient/tests/unit/identity/v3/test_oauth.py +5 -5
  206. openstackclient/tests/unit/identity/v3/test_project.py +16 -16
  207. openstackclient/tests/unit/identity/v3/test_protocol.py +7 -7
  208. openstackclient/tests/unit/identity/v3/test_region.py +7 -7
  209. openstackclient/tests/unit/identity/v3/test_registered_limit.py +12 -13
  210. openstackclient/tests/unit/identity/v3/test_role.py +13 -13
  211. openstackclient/tests/unit/identity/v3/test_role_assignment.py +410 -331
  212. openstackclient/tests/unit/identity/v3/test_service.py +93 -97
  213. openstackclient/tests/unit/identity/v3/test_service_provider.py +7 -7
  214. openstackclient/tests/unit/identity/v3/test_token.py +4 -4
  215. openstackclient/tests/unit/identity/v3/test_trust.py +9 -9
  216. openstackclient/tests/unit/identity/v3/test_unscoped_saml.py +4 -4
  217. openstackclient/tests/unit/identity/v3/test_user.py +299 -327
  218. openstackclient/tests/unit/image/v1/test_image.py +6 -6
  219. openstackclient/tests/unit/image/v2/fakes.py +46 -9
  220. openstackclient/tests/unit/image/v2/test_cache.py +2 -2
  221. openstackclient/tests/unit/image/v2/test_image.py +3 -3
  222. openstackclient/tests/unit/image/v2/test_metadef_objects.py +62 -0
  223. openstackclient/tests/unit/image/v2/test_metadef_resource_type_association.py +131 -0
  224. openstackclient/tests/unit/integ/base.py +1 -1
  225. openstackclient/tests/unit/integ/cli/test_project.py +4 -4
  226. openstackclient/tests/unit/integ/cli/test_shell.py +7 -7
  227. openstackclient/tests/unit/network/test_common.py +12 -21
  228. openstackclient/tests/unit/network/v2/fakes.py +64 -130
  229. openstackclient/tests/unit/network/v2/test_address_group.py +15 -15
  230. openstackclient/tests/unit/network/v2/test_address_scope.py +13 -13
  231. openstackclient/tests/unit/network/v2/test_default_security_group_rule.py +49 -27
  232. openstackclient/tests/unit/network/v2/test_floating_ip_compute.py +40 -38
  233. openstackclient/tests/unit/network/v2/test_floating_ip_network.py +15 -15
  234. openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py +4 -7
  235. openstackclient/tests/unit/network/v2/test_floating_ip_pool_network.py +3 -5
  236. openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py +11 -11
  237. openstackclient/tests/unit/network/v2/test_ip_availability.py +6 -6
  238. openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py +11 -21
  239. openstackclient/tests/unit/network/v2/test_local_ip.py +7 -7
  240. openstackclient/tests/unit/network/v2/test_local_ip_association.py +3 -5
  241. openstackclient/tests/unit/network/v2/test_ndp_proxy.py +13 -13
  242. openstackclient/tests/unit/network/v2/test_network.py +23 -28
  243. openstackclient/tests/unit/network/v2/test_network_agent.py +17 -21
  244. openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py +8 -8
  245. openstackclient/tests/unit/network/v2/test_network_compute.py +66 -65
  246. openstackclient/tests/unit/network/v2/test_network_flavor.py +17 -19
  247. openstackclient/tests/unit/network/v2/test_network_flavor_profile.py +13 -13
  248. openstackclient/tests/unit/network/v2/test_network_meter.py +11 -11
  249. openstackclient/tests/unit/network/v2/test_network_meter_rule.py +11 -11
  250. openstackclient/tests/unit/network/v2/test_network_qos_policy.py +11 -21
  251. openstackclient/tests/unit/network/v2/test_network_qos_rule.py +51 -77
  252. openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py +5 -9
  253. openstackclient/tests/unit/network/v2/test_network_rbac.py +12 -12
  254. openstackclient/tests/unit/network/v2/test_network_segment.py +11 -15
  255. openstackclient/tests/unit/network/v2/test_network_segment_range.py +11 -13
  256. openstackclient/tests/unit/network/v2/test_network_service_provider.py +3 -5
  257. openstackclient/tests/unit/network/v2/test_network_trunk.py +11 -11
  258. openstackclient/tests/unit/network/v2/test_port.py +22 -25
  259. openstackclient/tests/unit/network/v2/test_router.py +721 -51
  260. openstackclient/tests/unit/network/v2/test_security_group_compute.py +65 -49
  261. openstackclient/tests/unit/network/v2/test_security_group_network.py +15 -15
  262. openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +57 -45
  263. openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +11 -19
  264. openstackclient/tests/unit/network/v2/test_subnet.py +29 -25
  265. openstackclient/tests/unit/network/v2/test_subnet_pool.py +15 -15
  266. openstackclient/tests/unit/object/v1/fakes.py +1 -1
  267. openstackclient/tests/unit/object/v1/test_container.py +5 -5
  268. openstackclient/tests/unit/object/v1/test_container_all.py +6 -6
  269. openstackclient/tests/unit/object/v1/test_object.py +3 -3
  270. openstackclient/tests/unit/object/v1/test_object_all.py +5 -5
  271. openstackclient/tests/unit/test_shell.py +5 -5
  272. openstackclient/tests/unit/utils.py +4 -1
  273. openstackclient/tests/unit/volume/test_find_resource.py +2 -2
  274. openstackclient/tests/unit/volume/v1/fakes.py +5 -6
  275. openstackclient/tests/unit/volume/v1/test_volume.py +5 -4
  276. openstackclient/tests/unit/volume/v2/fakes.py +39 -259
  277. openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py +5 -5
  278. openstackclient/tests/unit/volume/v2/test_qos_specs.py +9 -9
  279. openstackclient/tests/unit/volume/v2/test_volume.py +21 -87
  280. openstackclient/tests/unit/volume/v2/test_volume_backup.py +7 -368
  281. openstackclient/tests/unit/volume/v2/test_volume_snapshot.py +1 -1
  282. openstackclient/tests/unit/volume/v2/test_volume_transfer_request.py +0 -44
  283. openstackclient/tests/unit/volume/v2/test_volume_type.py +6 -87
  284. openstackclient/tests/unit/volume/v3/fakes.py +505 -22
  285. openstackclient/tests/unit/volume/v3/test_block_storage_cleanup.py +2 -3
  286. openstackclient/tests/unit/volume/v3/test_block_storage_cluster.py +10 -11
  287. openstackclient/tests/unit/volume/v3/test_block_storage_log_level.py +10 -6
  288. openstackclient/tests/unit/volume/v3/test_block_storage_manage.py +25 -17
  289. openstackclient/tests/unit/volume/v3/test_block_storage_resource_filter.py +6 -32
  290. openstackclient/tests/unit/volume/v3/test_service.py +271 -0
  291. openstackclient/tests/unit/volume/v3/test_volume.py +2177 -33
  292. openstackclient/tests/unit/volume/v3/test_volume_attachment.py +48 -52
  293. openstackclient/tests/unit/volume/v3/test_volume_backup.py +892 -0
  294. openstackclient/tests/unit/volume/v3/test_volume_group.py +19 -20
  295. openstackclient/tests/unit/volume/v3/test_volume_group_snapshot.py +14 -34
  296. openstackclient/tests/unit/volume/v3/test_volume_group_type.py +13 -16
  297. openstackclient/tests/unit/volume/v3/test_volume_message.py +10 -11
  298. openstackclient/tests/unit/volume/v3/test_volume_snapshot.py +161 -0
  299. openstackclient/tests/unit/volume/v3/test_volume_transfer_request.py +425 -0
  300. openstackclient/tests/unit/volume/v3/test_volume_type.py +1109 -0
  301. openstackclient/volume/v1/qos_specs.py +7 -7
  302. openstackclient/volume/v1/service.py +2 -2
  303. openstackclient/volume/v1/volume.py +12 -12
  304. openstackclient/volume/v1/volume_backup.py +7 -7
  305. openstackclient/volume/v1/volume_snapshot.py +8 -8
  306. openstackclient/volume/v1/volume_transfer_request.py +5 -5
  307. openstackclient/volume/v1/volume_type.py +7 -7
  308. openstackclient/volume/v2/backup_record.py +2 -2
  309. openstackclient/volume/v2/consistency_group.py +7 -9
  310. openstackclient/volume/v2/consistency_group_snapshot.py +4 -12
  311. openstackclient/volume/v2/qos_specs.py +7 -7
  312. openstackclient/volume/v2/service.py +2 -2
  313. openstackclient/volume/v2/volume.py +80 -80
  314. openstackclient/volume/v2/volume_backend.py +2 -2
  315. openstackclient/volume/v2/volume_backup.py +7 -217
  316. openstackclient/volume/v2/volume_host.py +2 -2
  317. openstackclient/volume/v2/volume_snapshot.py +8 -8
  318. openstackclient/volume/v2/volume_transfer_request.py +5 -37
  319. openstackclient/volume/v2/volume_type.py +7 -89
  320. openstackclient/volume/v3/service.py +56 -0
  321. openstackclient/volume/v3/volume.py +971 -0
  322. openstackclient/volume/v3/volume_attachment.py +31 -29
  323. openstackclient/volume/v3/volume_backup.py +670 -0
  324. openstackclient/volume/v3/volume_message.py +1 -1
  325. openstackclient/volume/v3/volume_snapshot.py +97 -0
  326. openstackclient/volume/v3/volume_transfer_request.py +233 -0
  327. openstackclient/volume/v3/volume_type.py +967 -0
  328. {python_openstackclient-6.6.0.dist-info → python_openstackclient-7.0.0.dist-info}/AUTHORS +4 -0
  329. {python_openstackclient-6.6.0.dist-info → python_openstackclient-7.0.0.dist-info}/METADATA +3 -3
  330. python_openstackclient-7.0.0.dist-info/RECORD +502 -0
  331. {python_openstackclient-6.6.0.dist-info → python_openstackclient-7.0.0.dist-info}/entry_points.txt +33 -27
  332. python_openstackclient-7.0.0.dist-info/pbr.json +1 -0
  333. python_openstackclient-6.6.0.dist-info/RECORD +0 -489
  334. python_openstackclient-6.6.0.dist-info/pbr.json +0 -1
  335. {python_openstackclient-6.6.0.dist-info → python_openstackclient-7.0.0.dist-info}/LICENSE +0 -0
  336. {python_openstackclient-6.6.0.dist-info → python_openstackclient-7.0.0.dist-info}/WHEEL +0 -0
  337. {python_openstackclient-6.6.0.dist-info → python_openstackclient-7.0.0.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,8 @@
13
13
 
14
14
  """Router action implementations"""
15
15
 
16
+ import argparse
17
+ import collections
16
18
  import copy
17
19
  import json
18
20
  import logging
@@ -85,8 +87,120 @@ def _get_columns(item):
85
87
  )
86
88
 
87
89
 
90
+ def is_multiple_gateways_supported(n_client):
91
+ return n_client.find_extension("external-gateway-multihoming") is not None
92
+
93
+
94
+ def _passed_multiple_gateways(extension_supported, external_gateways):
95
+ passed_multiple_gws = len(external_gateways) > 1
96
+ if passed_multiple_gws and not extension_supported:
97
+ msg = _(
98
+ 'Supplying --external-gateway option multiple times is not '
99
+ 'supported due to the lack of external-gateway-multihoming '
100
+ 'extension at the Neutron side.'
101
+ )
102
+ raise exceptions.CommandError(msg)
103
+ return passed_multiple_gws
104
+
105
+
106
+ def _get_external_gateway_attrs(client_manager, parsed_args):
107
+ attrs = {}
108
+
109
+ if parsed_args.external_gateways:
110
+ external_gateways: collections.defaultdict[str, list[dict]] = (
111
+ collections.defaultdict(list)
112
+ )
113
+ n_client = client_manager.network
114
+ first_network_id = None
115
+
116
+ for gw_net_name_or_id in parsed_args.external_gateways:
117
+ gateway_info = {}
118
+ gw_net = n_client.find_network(
119
+ gw_net_name_or_id, ignore_missing=False
120
+ )
121
+ if first_network_id is None:
122
+ first_network_id = gw_net.id
123
+ gateway_info['network_id'] = gw_net.id
124
+ if 'disable_snat' in parsed_args and parsed_args.disable_snat:
125
+ gateway_info['enable_snat'] = False
126
+ if 'enable_snat' in parsed_args and parsed_args.enable_snat:
127
+ gateway_info['enable_snat'] = True
128
+
129
+ # This option was added before multiple gateways were supported, so
130
+ # it does not have a per-gateway port granularity so just pass it
131
+ # along in gw info in case it is specified.
132
+ if 'qos_policy' in parsed_args and parsed_args.qos_policy:
133
+ qos_id = n_client.find_qos_policy(
134
+ parsed_args.qos_policy, ignore_missing=False
135
+ ).id
136
+ gateway_info['qos_policy_id'] = qos_id
137
+ if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy:
138
+ gateway_info['qos_policy_id'] = None
139
+
140
+ external_gateways[gw_net.id].append(gateway_info)
141
+
142
+ multiple_gws_supported = is_multiple_gateways_supported(n_client)
143
+ # Parse the external fixed IP specs and match them to specific gateway
144
+ # ports if needed.
145
+ if parsed_args.fixed_ips:
146
+ for ip_spec in parsed_args.fixed_ips:
147
+ # If there is only one gateway, this value will represent the
148
+ # network ID for it, otherwise it will be overridden.
149
+ ip_net_id = first_network_id
150
+
151
+ if ip_spec.get('subnet', False):
152
+ subnet_name_id = ip_spec.pop('subnet')
153
+ if subnet_name_id:
154
+ subnet = n_client.find_subnet(
155
+ subnet_name_id, ignore_missing=False
156
+ )
157
+ ip_spec['subnet_id'] = subnet.id
158
+ ip_net_id = subnet.network_id
159
+ if ip_spec.get('ip-address', False):
160
+ ip_spec['ip_address'] = ip_spec.pop('ip-address')
161
+ # Finally, add an ip_spec to the specific gateway identified
162
+ # by a network from the spec.
163
+ if (
164
+ 'subnet_id' in ip_spec
165
+ and ip_net_id not in external_gateways
166
+ ):
167
+ msg = _(
168
+ 'Subnet %s does not belong to any of the networks '
169
+ 'provided for --external-gateway.'
170
+ ) % (ip_spec['subnet_id'])
171
+ raise exceptions.CommandError(msg)
172
+ for gw_info in external_gateways[ip_net_id]:
173
+ if 'external_fixed_ips' not in gw_info:
174
+ gw_info['external_fixed_ips'] = [ip_spec]
175
+ break
176
+ else:
177
+ # The end user has requested more fixed IPs than there are
178
+ # gateways, add multiple fixed IPs to single gateway to
179
+ # retain current behavior.
180
+ for gw_info in external_gateways[ip_net_id]:
181
+ gw_info['external_fixed_ips'].append(ip_spec)
182
+ break
183
+
184
+ # Use the newer API whenever it is supported regardless of whether one
185
+ # or multiple gateways are passed as arguments.
186
+ if multiple_gws_supported:
187
+ gateway_list = []
188
+ # Now merge the per-network-id lists of external gateway info
189
+ # dicts into one list.
190
+ for gw_info_list in external_gateways.values():
191
+ gateway_list.extend(gw_info_list)
192
+ attrs['external_gateways'] = gateway_list
193
+ else:
194
+ attrs['external_gateway_info'] = external_gateways[
195
+ first_network_id
196
+ ][0]
197
+ return attrs
198
+
199
+
88
200
  def _get_attrs(client_manager, parsed_args):
89
201
  attrs = {}
202
+ n_client = client_manager.network
203
+
90
204
  if parsed_args.name is not None:
91
205
  attrs['name'] = parsed_args.name
92
206
  if parsed_args.enable:
@@ -113,44 +227,85 @@ def _get_attrs(client_manager, parsed_args):
113
227
  parsed_args.project_domain,
114
228
  ).id
115
229
  attrs['project_id'] = project_id
116
- if parsed_args.external_gateway:
117
- gateway_info = {}
118
- n_client = client_manager.network
119
- network = n_client.find_network(
120
- parsed_args.external_gateway, ignore_missing=False
121
- )
122
- gateway_info['network_id'] = network.id
123
- if parsed_args.disable_snat:
124
- gateway_info['enable_snat'] = False
125
- if parsed_args.enable_snat:
126
- gateway_info['enable_snat'] = True
127
- if parsed_args.fixed_ip:
128
- ips = []
129
- for ip_spec in parsed_args.fixed_ip:
130
- if ip_spec.get('subnet', False):
131
- subnet_name_id = ip_spec.pop('subnet')
132
- if subnet_name_id:
133
- subnet = n_client.find_subnet(
134
- subnet_name_id, ignore_missing=False
135
- )
136
- ip_spec['subnet_id'] = subnet.id
137
- if ip_spec.get('ip-address', False):
138
- ip_spec['ip_address'] = ip_spec.pop('ip-address')
139
- ips.append(ip_spec)
140
- gateway_info['external_fixed_ips'] = ips
141
- attrs['external_gateway_info'] = gateway_info
230
+
231
+ attrs.update(_get_external_gateway_attrs(client_manager, parsed_args))
232
+
142
233
  # "router set" command doesn't support setting flavor_id.
143
234
  if 'flavor_id' in parsed_args and parsed_args.flavor_id is not None:
144
- attrs['flavor_id'] = parsed_args.flavor_id
235
+ flavor = n_client.find_flavor(parsed_args.flavor_id)
236
+ attrs['flavor_id'] = flavor.id
237
+
238
+ for attr in ('enable_default_route_bfd', 'enable_default_route_ecmp'):
239
+ value = getattr(parsed_args, attr, None)
240
+ if value is not None:
241
+ attrs[attr] = value
145
242
 
146
243
  return attrs
147
244
 
148
245
 
246
+ def _parser_add_bfd_ecmp_arguments(parser):
247
+ """Helper to add BFD and ECMP args for CreateRouter and SetRouter."""
248
+ parser.add_argument(
249
+ '--enable-default-route-bfd',
250
+ dest='enable_default_route_bfd',
251
+ default=None,
252
+ action='store_true',
253
+ help=_(
254
+ "Enable BFD sessions for default routes inferred from "
255
+ "the external gateway port subnets for this router."
256
+ ),
257
+ )
258
+ parser.add_argument(
259
+ '--disable-default-route-bfd',
260
+ dest='enable_default_route_bfd',
261
+ default=None,
262
+ action='store_false',
263
+ help=_(
264
+ "Disable BFD sessions for default routes inferred from "
265
+ "the external gateway port subnets for this router."
266
+ ),
267
+ )
268
+ parser.add_argument(
269
+ '--enable-default-route-ecmp',
270
+ dest='enable_default_route_ecmp',
271
+ default=None,
272
+ action='store_true',
273
+ help=_(
274
+ "Add ECMP default routes if multiple are available via "
275
+ "different gateway ports."
276
+ ),
277
+ )
278
+ parser.add_argument(
279
+ '--disable-default-route-ecmp',
280
+ dest='enable_default_route_ecmp',
281
+ default=None,
282
+ action='store_false',
283
+ help=_("Add default route only for first gateway port."),
284
+ )
285
+
286
+
287
+ def _command_check_bfd_ecmp_supported(attrs, client):
288
+ """Helper to check for server side support when bfd/ecmp attrs provided.
289
+
290
+ :raises: exceptions.CommandError
291
+ """
292
+ if (
293
+ 'enable_default_route_bfd' in attrs
294
+ or 'enable_default_route_ecmp' in attrs
295
+ ) and not is_multiple_gateways_supported(client):
296
+ msg = _(
297
+ 'The external-gateway-multihoming extension is not enabled at '
298
+ 'the Neutron side, cannot use --enable-default-route-bfd or '
299
+ '--enable-default-route-ecmp arguments.'
300
+ )
301
+ raise exceptions.CommandError(msg)
302
+
303
+
149
304
  class AddPortToRouter(command.Command):
150
305
  _description = _("Add a port to a router")
151
306
 
152
307
  def get_parser(self, prog_name):
153
- parser = super(AddPortToRouter, self).get_parser(prog_name)
308
+ parser = super().get_parser(prog_name)
154
309
  parser.add_argument(
155
310
  'router',
156
311
  metavar='<router>',
@@ -174,7 +329,7 @@ class AddSubnetToRouter(command.Command):
174
329
  _description = _("Add a subnet to a router")
175
330
 
176
331
  def get_parser(self, prog_name):
177
- parser = super(AddSubnetToRouter, self).get_parser(prog_name)
332
+ parser = super().get_parser(prog_name)
178
333
  parser.add_argument(
179
334
  'router',
180
335
  metavar='<router>',
@@ -200,7 +355,7 @@ class AddExtraRoutesToRouter(command.ShowOne):
200
355
  _description = _("Add extra static routes to a router's routing table.")
201
356
 
202
357
  def get_parser(self, prog_name):
203
- parser = super(AddExtraRoutesToRouter, self).get_parser(prog_name)
358
+ parser = super().get_parser(prog_name)
204
359
  parser.add_argument(
205
360
  'router',
206
361
  metavar='<router>',
@@ -251,7 +406,7 @@ class RemoveExtraRoutesFromRouter(command.ShowOne):
251
406
  )
252
407
 
253
408
  def get_parser(self, prog_name):
254
- parser = super(RemoveExtraRoutesFromRouter, self).get_parser(prog_name)
409
+ parser = super().get_parser(prog_name)
255
410
  parser.add_argument(
256
411
  'router',
257
412
  metavar='<router>',
@@ -302,7 +457,7 @@ class CreateRouter(command.ShowOne, common.NeutronCommandWithExtraArgs):
302
457
  _description = _("Create a new router")
303
458
 
304
459
  def get_parser(self, prog_name):
305
- parser = super(CreateRouter, self).get_parser(prog_name)
460
+ parser = super().get_parser(prog_name)
306
461
  parser.add_argument(
307
462
  'name', metavar='<name>', help=_("New router name")
308
463
  )
@@ -362,18 +517,25 @@ class CreateRouter(command.ShowOne, common.NeutronCommandWithExtraArgs):
362
517
  parser.add_argument(
363
518
  '--external-gateway',
364
519
  metavar="<network>",
365
- help=_("External Network used as router's gateway (name or ID)"),
520
+ action='append',
521
+ help=_(
522
+ "External Network used as router's gateway (name or ID). "
523
+ "(repeat option to set multiple gateways per router "
524
+ "if the L3 service plugin in use supports it)."
525
+ ),
526
+ dest='external_gateways',
366
527
  )
367
528
  parser.add_argument(
368
529
  '--fixed-ip',
369
530
  metavar='subnet=<subnet>,ip-address=<ip-address>',
370
531
  action=parseractions.MultiKeyValueAction,
371
532
  optional_keys=['subnet', 'ip-address'],
533
+ dest='fixed_ips',
372
534
  help=_(
373
535
  "Desired IP and/or subnet (name or ID) "
374
536
  "on external gateway: "
375
537
  "subnet=<subnet>,ip-address=<ip-address> "
376
- "(repeat option to set multiple fixed IP addresses)"
538
+ "(repeat option to set multiple fixed IP addresses)."
377
539
  ),
378
540
  )
379
541
  snat_group = parser.add_mutually_exclusive_group()
@@ -402,11 +564,17 @@ class CreateRouter(command.ShowOne, common.NeutronCommandWithExtraArgs):
402
564
  action='store_false',
403
565
  help=_("Disable IPv6 NDP proxy on external gateway"),
404
566
  )
567
+ parser.add_argument(
568
+ '--flavor',
569
+ metavar='<flavor-id>',
570
+ help=_("Associate the router to a flavor (by name or ID"),
571
+ )
405
572
  parser.add_argument(
406
573
  '--flavor-id',
407
574
  metavar='<flavor-id>',
408
- help=_("Associate the router to a flavor by ID"),
575
+ help=argparse.SUPPRESS,
409
576
  )
577
+ _parser_add_bfd_ecmp_arguments(parser)
410
578
 
411
579
  return parser
412
580
 
@@ -422,7 +590,7 @@ class CreateRouter(command.ShowOne, common.NeutronCommandWithExtraArgs):
422
590
  self._parse_extra_properties(parsed_args.extra_properties)
423
591
  )
424
592
 
425
- if parsed_args.enable_ndp_proxy and not parsed_args.external_gateway:
593
+ if parsed_args.enable_ndp_proxy and not parsed_args.external_gateways:
426
594
  msg = _(
427
595
  "You must specify '--external-gateway' in order "
428
596
  "to enable router's NDP proxy"
@@ -432,15 +600,26 @@ class CreateRouter(command.ShowOne, common.NeutronCommandWithExtraArgs):
432
600
  if parsed_args.enable_ndp_proxy is not None:
433
601
  attrs['enable_ndp_proxy'] = parsed_args.enable_ndp_proxy
434
602
 
603
+ _command_check_bfd_ecmp_supported(attrs, client)
604
+
605
+ external_gateways = attrs.pop('external_gateways', None)
435
606
  obj = client.create_router(**attrs)
436
607
  # tags cannot be set when created, so tags need to be set later.
437
608
  _tag.update_tags_for_set(client, obj, parsed_args)
438
609
 
610
+ # If the multiple external gateways API is intended to be used,
611
+ # do a separate API call to set the desired external gateways as the
612
+ # router creation API supports adding only one.
613
+ if external_gateways:
614
+ client.update_external_gateways(
615
+ obj, body={'router': {'external_gateways': external_gateways}}
616
+ )
617
+
439
618
  if (
440
619
  parsed_args.disable_snat
441
620
  or parsed_args.enable_snat
442
- or parsed_args.fixed_ip
443
- ) and not parsed_args.external_gateway:
621
+ or parsed_args.fixed_ips
622
+ ) and not parsed_args.external_gateways:
444
623
  msg = _(
445
624
  "You must specify '--external-gateway' in order "
446
625
  "to specify SNAT or fixed-ip values"
@@ -457,7 +636,7 @@ class DeleteRouter(command.Command):
457
636
  _description = _("Delete router(s)")
458
637
 
459
638
  def get_parser(self, prog_name):
460
- parser = super(DeleteRouter, self).get_parser(prog_name)
639
+ parser = super().get_parser(prog_name)
461
640
  parser.add_argument(
462
641
  'router',
463
642
  metavar="<router>",
@@ -499,7 +678,7 @@ class ListRouter(command.Lister):
499
678
  _description = _("List routers")
500
679
 
501
680
  def get_parser(self, prog_name):
502
- parser = super(ListRouter, self).get_parser(prog_name)
681
+ parser = super().get_parser(prog_name)
503
682
  parser.add_argument(
504
683
  '--name',
505
684
  metavar='<name>',
@@ -643,7 +822,7 @@ class RemovePortFromRouter(command.Command):
643
822
  _description = _("Remove a port from a router")
644
823
 
645
824
  def get_parser(self, prog_name):
646
- parser = super(RemovePortFromRouter, self).get_parser(prog_name)
825
+ parser = super().get_parser(prog_name)
647
826
  parser.add_argument(
648
827
  'router',
649
828
  metavar='<router>',
@@ -669,7 +848,7 @@ class RemoveSubnetFromRouter(command.Command):
669
848
  _description = _("Remove a subnet from a router")
670
849
 
671
850
  def get_parser(self, prog_name):
672
- parser = super(RemoveSubnetFromRouter, self).get_parser(prog_name)
851
+ parser = super().get_parser(prog_name)
673
852
  parser.add_argument(
674
853
  'router',
675
854
  metavar='<router>',
@@ -699,7 +878,7 @@ class SetRouter(common.NeutronCommandWithExtraArgs):
699
878
  _description = _("Set router properties")
700
879
 
701
880
  def get_parser(self, prog_name):
702
- parser = super(SetRouter, self).get_parser(prog_name)
881
+ parser = super().get_parser(prog_name)
703
882
  parser.add_argument(
704
883
  'router',
705
884
  metavar="<router>",
@@ -780,18 +959,25 @@ class SetRouter(common.NeutronCommandWithExtraArgs):
780
959
  parser.add_argument(
781
960
  '--external-gateway',
782
961
  metavar="<network>",
783
- help=_("External Network used as router's gateway (name or ID)"),
962
+ action='append',
963
+ help=_(
964
+ "External Network used as router's gateway (name or ID). "
965
+ "(repeat option to set multiple gateways per router "
966
+ "if the L3 service plugin in use supports it)."
967
+ ),
968
+ dest='external_gateways',
784
969
  )
785
970
  parser.add_argument(
786
971
  '--fixed-ip',
787
972
  metavar='subnet=<subnet>,ip-address=<ip-address>',
788
973
  action=parseractions.MultiKeyValueAction,
789
974
  optional_keys=['subnet', 'ip-address'],
975
+ dest='fixed_ips',
790
976
  help=_(
791
977
  "Desired IP and/or subnet (name or ID) "
792
978
  "on external gateway: "
793
979
  "subnet=<subnet>,ip-address=<ip-address> "
794
- "(repeat option to set multiple fixed IP addresses)"
980
+ "(repeat option to set multiple fixed IP addresses)."
795
981
  ),
796
982
  )
797
983
  snat_group = parser.add_mutually_exclusive_group()
@@ -832,6 +1018,7 @@ class SetRouter(common.NeutronCommandWithExtraArgs):
832
1018
  help=_("Remove QoS policy from router gateway IPs"),
833
1019
  )
834
1020
  _tag.add_tag_option_to_parser_for_set(parser, _('router'))
1021
+ _parser_add_bfd_ecmp_arguments(parser)
835
1022
  return parser
836
1023
 
837
1024
  def take_action(self, parsed_args):
@@ -860,8 +1047,8 @@ class SetRouter(common.NeutronCommandWithExtraArgs):
860
1047
  if (
861
1048
  parsed_args.disable_snat
862
1049
  or parsed_args.enable_snat
863
- or parsed_args.fixed_ip
864
- ) and not parsed_args.external_gateway:
1050
+ or parsed_args.fixed_ips
1051
+ ) and not parsed_args.external_gateways:
865
1052
  msg = _(
866
1053
  "You must specify '--external-gateway' in order "
867
1054
  "to update the SNAT or fixed-ip values"
@@ -870,7 +1057,7 @@ class SetRouter(common.NeutronCommandWithExtraArgs):
870
1057
 
871
1058
  if (
872
1059
  parsed_args.qos_policy or parsed_args.no_qos_policy
873
- ) and not parsed_args.external_gateway:
1060
+ ) and not parsed_args.external_gateways:
874
1061
  try:
875
1062
  original_net_id = obj.external_gateway_info['network_id']
876
1063
  except (KeyError, TypeError):
@@ -881,17 +1068,21 @@ class SetRouter(common.NeutronCommandWithExtraArgs):
881
1068
  )
882
1069
  raise exceptions.CommandError(msg)
883
1070
  else:
884
- if not attrs.get('external_gateway_info'):
1071
+ if not attrs.get('external_gateway_info') and not attrs.get(
1072
+ 'external_gateways'
1073
+ ):
885
1074
  attrs['external_gateway_info'] = {}
886
1075
  attrs['external_gateway_info']['network_id'] = original_net_id
887
1076
  if parsed_args.qos_policy:
888
1077
  check_qos_id = client.find_qos_policy(
889
1078
  parsed_args.qos_policy, ignore_missing=False
890
1079
  ).id
891
- attrs['external_gateway_info']['qos_policy_id'] = check_qos_id
1080
+ if not attrs.get('external_gateways'):
1081
+ attrs['external_gateway_info']['qos_policy_id'] = check_qos_id
892
1082
 
893
1083
  if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy:
894
- attrs['external_gateway_info']['qos_policy_id'] = None
1084
+ if not attrs.get('external_gateways'):
1085
+ attrs['external_gateway_info']['qos_policy_id'] = None
895
1086
 
896
1087
  attrs.update(
897
1088
  self._parse_extra_properties(parsed_args.extra_properties)
@@ -900,8 +1091,19 @@ class SetRouter(common.NeutronCommandWithExtraArgs):
900
1091
  if parsed_args.enable_ndp_proxy is not None:
901
1092
  attrs['enable_ndp_proxy'] = parsed_args.enable_ndp_proxy
902
1093
 
1094
+ _command_check_bfd_ecmp_supported(attrs, client)
1095
+
903
1096
  if attrs:
1097
+ external_gateways = attrs.pop('external_gateways', None)
904
1098
  client.update_router(obj, **attrs)
1099
+ # If the multiple external gateways API is intended to be used,
1100
+ # do a separate API call to set external gateways.
1101
+ if external_gateways:
1102
+ client.update_external_gateways(
1103
+ obj,
1104
+ body={'router': {'external_gateways': external_gateways}},
1105
+ )
1106
+
905
1107
  # tags is a subresource and it needs to be updated separately.
906
1108
  _tag.update_tags_for_set(client, obj, parsed_args)
907
1109
 
@@ -910,7 +1112,7 @@ class ShowRouter(command.ShowOne):
910
1112
  _description = _("Display router details")
911
1113
 
912
1114
  def get_parser(self, prog_name):
913
- parser = super(ShowRouter, self).get_parser(prog_name)
1115
+ parser = super().get_parser(prog_name)
914
1116
  parser.add_argument(
915
1117
  'router',
916
1118
  metavar="<router>",
@@ -946,7 +1148,7 @@ class UnsetRouter(common.NeutronUnsetCommandWithExtraArgs):
946
1148
  _description = _("Unset router properties")
947
1149
 
948
1150
  def get_parser(self, prog_name):
949
- parser = super(UnsetRouter, self).get_parser(prog_name)
1151
+ parser = super().get_parser(prog_name)
950
1152
  parser.add_argument(
951
1153
  '--route',
952
1154
  metavar='destination=<subnet>,gateway=<ip-address>',
@@ -961,11 +1163,15 @@ class UnsetRouter(common.NeutronUnsetCommandWithExtraArgs):
961
1163
  "(repeat option to unset multiple routes)"
962
1164
  ),
963
1165
  )
1166
+ # NOTE(dmitriis): This was not extended to support selective removal
1167
+ # of external gateways due to a cpython bug in argparse:
1168
+ # https://github.com/python/cpython/issues/53584
964
1169
  parser.add_argument(
965
1170
  '--external-gateway',
966
1171
  action='store_true',
967
1172
  default=False,
968
1173
  help=_("Remove external gateway information from the router"),
1174
+ dest='external_gateways',
969
1175
  )
970
1176
  parser.add_argument(
971
1177
  '--qos-policy',
@@ -1012,7 +1218,7 @@ class UnsetRouter(common.NeutronUnsetCommandWithExtraArgs):
1012
1218
  'qos_policy_id': None,
1013
1219
  }
1014
1220
 
1015
- if parsed_args.external_gateway:
1221
+ if parsed_args.external_gateways:
1016
1222
  attrs['external_gateway_info'] = {}
1017
1223
 
1018
1224
  attrs.update(
@@ -1020,6 +1226,149 @@ class UnsetRouter(common.NeutronUnsetCommandWithExtraArgs):
1020
1226
  )
1021
1227
 
1022
1228
  if attrs:
1229
+ # If removing multiple gateways per router are supported,
1230
+ # use the relevant API to remove them all.
1231
+ if is_multiple_gateways_supported(client):
1232
+ client.remove_external_gateways(
1233
+ obj,
1234
+ body={'router': {'external_gateways': {}}},
1235
+ )
1236
+
1023
1237
  client.update_router(obj, **attrs)
1024
1238
  # tags is a subresource and it needs to be updated separately.
1025
1239
  _tag.update_tags_for_unset(client, obj, parsed_args)
1240
+
1241
+
1242
+ class AddGatewayToRouter(command.ShowOne):
1243
+ _description = _("Add router gateway")
1244
+
1245
+ def get_parser(self, prog_name):
1246
+ parser = super().get_parser(prog_name)
1247
+ parser.add_argument(
1248
+ 'router',
1249
+ metavar="<router>",
1250
+ help=_("Router to modify (name or ID)."),
1251
+ )
1252
+ parser.add_argument(
1253
+ metavar="<network>",
1254
+ help=_(
1255
+ "External Network to a attach a router gateway to (name or "
1256
+ "ID)."
1257
+ ),
1258
+ dest='external_gateways',
1259
+ # The argument is stored in a list in order to reuse the
1260
+ # common attribute parsing code.
1261
+ nargs=1,
1262
+ )
1263
+ parser.add_argument(
1264
+ '--fixed-ip',
1265
+ metavar='subnet=<subnet>,ip-address=<ip-address>',
1266
+ action=parseractions.MultiKeyValueAction,
1267
+ optional_keys=['subnet', 'ip-address'],
1268
+ dest='fixed_ips',
1269
+ help=_(
1270
+ "Desired IP and/or subnet (name or ID) "
1271
+ "on external gateway: "
1272
+ "subnet=<subnet>,ip-address=<ip-address> "
1273
+ "(repeat option to set multiple fixed IP addresses)."
1274
+ ),
1275
+ )
1276
+ return parser
1277
+
1278
+ def take_action(self, parsed_args):
1279
+ client = self.app.client_manager.network
1280
+ if not is_multiple_gateways_supported(client):
1281
+ msg = _(
1282
+ 'The external-gateway-multihoming extension is not enabled at '
1283
+ 'the Neutron side.'
1284
+ )
1285
+ raise exceptions.CommandError(msg)
1286
+
1287
+ router_obj = client.find_router(
1288
+ parsed_args.router, ignore_missing=False
1289
+ )
1290
+
1291
+ # Get the common attributes.
1292
+ attrs = _get_external_gateway_attrs(
1293
+ self.app.client_manager, parsed_args
1294
+ )
1295
+
1296
+ if attrs:
1297
+ external_gateways = attrs.pop('external_gateways')
1298
+ router_obj = client.add_external_gateways(
1299
+ router_obj,
1300
+ body={'router': {'external_gateways': external_gateways}},
1301
+ )
1302
+
1303
+ display_columns, columns = _get_columns(router_obj)
1304
+ data = utils.get_item_properties(
1305
+ router_obj, columns, formatters=_formatters
1306
+ )
1307
+ return (display_columns, data)
1308
+
1309
+
1310
+ class RemoveGatewayFromRouter(command.ShowOne):
1311
+ _description = _("Remove router gateway")
1312
+
1313
+ def get_parser(self, prog_name):
1314
+ parser = super().get_parser(prog_name)
1315
+ parser.add_argument(
1316
+ 'router',
1317
+ metavar="<router>",
1318
+ help=_("Router to modify (name or ID)."),
1319
+ )
1320
+ parser.add_argument(
1321
+ metavar="<network>",
1322
+ help=_(
1323
+ "External Network to remove a router gateway from (name or "
1324
+ "ID)."
1325
+ ),
1326
+ dest='external_gateways',
1327
+ # The argument is stored in a list in order to reuse the
1328
+ # common attribute parsing code.
1329
+ nargs=1,
1330
+ )
1331
+ parser.add_argument(
1332
+ '--fixed-ip',
1333
+ metavar='subnet=<subnet>,ip-address=<ip-address>',
1334
+ action=parseractions.MultiKeyValueAction,
1335
+ optional_keys=['subnet', 'ip-address'],
1336
+ dest='fixed_ips',
1337
+ help=_(
1338
+ "IP and/or subnet (name or ID) on the external gateway "
1339
+ "which is used to identify a particular gateway if multiple "
1340
+ "are attached to the same network: subnet=<subnet>,"
1341
+ "ip-address=<ip-address>."
1342
+ ),
1343
+ )
1344
+ return parser
1345
+
1346
+ def take_action(self, parsed_args):
1347
+ client = self.app.client_manager.network
1348
+ if not is_multiple_gateways_supported(client):
1349
+ msg = _(
1350
+ 'The external-gateway-multihoming extension is not enabled at '
1351
+ 'the Neutron side.'
1352
+ )
1353
+ raise exceptions.CommandError(msg)
1354
+
1355
+ router_obj = client.find_router(
1356
+ parsed_args.router, ignore_missing=False
1357
+ )
1358
+
1359
+ # Get the common attributes.
1360
+ attrs = _get_external_gateway_attrs(
1361
+ self.app.client_manager, parsed_args
1362
+ )
1363
+ if attrs:
1364
+ external_gateways = attrs.pop('external_gateways')
1365
+ router_obj = client.remove_external_gateways(
1366
+ router_obj,
1367
+ body={'router': {'external_gateways': external_gateways}},
1368
+ )
1369
+
1370
+ display_columns, columns = _get_columns(router_obj)
1371
+ data = utils.get_item_properties(
1372
+ router_obj, columns, formatters=_formatters
1373
+ )
1374
+ return (display_columns, data)