summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Goirand <zigo@debian.org>2016-09-13 18:57:24 +0200
committergit-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com>2016-10-05 16:10:32 +0000
commit22d73e12143d38d34c42df64561fd232c8f3cd36 (patch)
tree68a24910650ce8d697bdc765b399e53d03ac9098
parent55697a960c23b7796eb99a128e25a3811caa2c28 (diff)
parent8f8aaa6a706b92340d002d5a801bf8210861f329 (diff)
3.2.0-1 (patches applied)applied/3.2.0-1
Imported using git-ubuntu import.
-rw-r--r--README.rst9
-rw-r--r--debian/changelog15
-rw-r--r--debian/control98
-rw-r--r--debian/patches/0001-reproducible-build.patch16
-rw-r--r--debian/patches/series1
-rw-r--r--debian/tests/control3
-rwxr-xr-xdebian/tests/openstackclient45
-rw-r--r--doc/source/authentication.rst2
-rw-r--r--doc/source/backwards-incompatible.rst88
-rw-r--r--doc/source/command-beta.rst58
-rw-r--r--doc/source/command-errors.rst21
-rw-r--r--doc/source/command-logs.rst73
-rw-r--r--doc/source/command-objects/aggregate.rst13
-rw-r--r--doc/source/command-objects/backup.rst41
-rw-r--r--doc/source/command-objects/compute-agent.rst30
-rw-r--r--doc/source/command-objects/compute-service.rst21
-rw-r--r--doc/source/command-objects/console-url.rst14
-rw-r--r--doc/source/command-objects/consumer.rst6
-rw-r--r--doc/source/command-objects/credential.rst108
-rw-r--r--doc/source/command-objects/domain.rst6
-rw-r--r--doc/source/command-objects/ec2-credentials.rst4
-rw-r--r--doc/source/command-objects/endpoint.rst14
-rw-r--r--doc/source/command-objects/federation-protocol.rst6
-rw-r--r--doc/source/command-objects/flavor.rst23
-rw-r--r--doc/source/command-objects/floating-ip-pool.rst15
-rw-r--r--doc/source/command-objects/floating-ip.rst82
-rw-r--r--doc/source/command-objects/host.rst6
-rw-r--r--doc/source/command-objects/identity-provider.rst6
-rw-r--r--doc/source/command-objects/ip-availability.rst2
-rw-r--r--doc/source/command-objects/ip-fixed.rst2
-rw-r--r--doc/source/command-objects/ip-floating-pool.rst1
-rw-r--r--doc/source/command-objects/ip-floating.rst13
-rw-r--r--doc/source/command-objects/keypair.rst8
-rw-r--r--doc/source/command-objects/mapping.rst6
-rw-r--r--doc/source/command-objects/network-agent.rst83
-rw-r--r--doc/source/command-objects/network-rbac.rst123
-rw-r--r--doc/source/command-objects/network.rst32
-rw-r--r--doc/source/command-objects/policy.rst6
-rw-r--r--doc/source/command-objects/port.rst47
-rw-r--r--doc/source/command-objects/quota.rst12
-rw-r--r--doc/source/command-objects/region.rst6
-rw-r--r--doc/source/command-objects/role-assignment.rst36
-rw-r--r--doc/source/command-objects/role.rst65
-rw-r--r--doc/source/command-objects/router.rst26
-rw-r--r--doc/source/command-objects/security-group-rule.rst6
-rw-r--r--doc/source/command-objects/security-group.rst6
-rw-r--r--doc/source/command-objects/server-image.rst8
-rw-r--r--doc/source/command-objects/server.rst82
-rw-r--r--doc/source/command-objects/service-provider.rst6
-rw-r--r--doc/source/command-objects/service.rst8
-rw-r--r--doc/source/command-objects/snapshot.rst22
-rw-r--r--doc/source/command-objects/subnet-pool.rst28
-rw-r--r--doc/source/command-objects/subnet.rst70
-rw-r--r--doc/source/command-objects/user.rst3
-rw-r--r--doc/source/command-objects/volume-backup.rst132
-rw-r--r--doc/source/command-objects/volume-qos.rst7
-rw-r--r--doc/source/command-objects/volume-type.rst36
-rw-r--r--doc/source/command-objects/volume.rst17
-rw-r--r--doc/source/command-options.rst38
-rw-r--r--doc/source/commands.rst20
-rw-r--r--doc/source/configuration.rst56
-rw-r--r--doc/source/developing.rst98
-rw-r--r--doc/source/humaninterfaceguide.rst24
-rw-r--r--doc/source/index.rst1
-rw-r--r--doc/source/plugin-commands.rst95
-rw-r--r--doc/source/plugins.rst30
-rw-r--r--doc/source/specs/command-objects/example.rst2
-rwxr-xr-xdoc/source/specs/network-topology.rst44
-rwxr-xr-xexamples/common.py3
-rwxr-xr-xexamples/object_api.py4
-rwxr-xr-xexamples/osc-lib.py3
-rw-r--r--functional/common/test.py13
-rw-r--r--functional/tests/common/test_availability_zone.py2
-rw-r--r--functional/tests/common/test_help.py3
-rw-r--r--functional/tests/common/test_quota.py2
-rw-r--r--functional/tests/compute/v2/test_agent.py23
-rw-r--r--functional/tests/compute/v2/test_aggregate.py18
-rw-r--r--functional/tests/compute/v2/test_flavor.py24
-rw-r--r--functional/tests/compute/v2/test_server.py57
-rw-r--r--functional/tests/compute/v2/test_server_group.py8
-rw-r--r--functional/tests/identity/v2/common.py (renamed from functional/tests/identity/v2/test_identity.py)12
-rw-r--r--functional/tests/identity/v2/test_catalog.py4
-rw-r--r--functional/tests/identity/v2/test_ec2_credentials.py12
-rw-r--r--functional/tests/identity/v2/test_endpoint.py11
-rw-r--r--functional/tests/identity/v2/test_project.py6
-rw-r--r--functional/tests/identity/v2/test_role.py8
-rw-r--r--functional/tests/identity/v2/test_service.py13
-rw-r--r--functional/tests/identity/v2/test_token.py4
-rw-r--r--functional/tests/identity/v2/test_user.py6
-rw-r--r--functional/tests/identity/v3/common.py (renamed from functional/tests/identity/v3/test_identity.py)12
-rw-r--r--functional/tests/identity/v3/test_catalog.py4
-rw-r--r--functional/tests/identity/v3/test_domain.py18
-rw-r--r--functional/tests/identity/v3/test_endpoint.py11
-rw-r--r--functional/tests/identity/v3/test_group.py8
-rw-r--r--functional/tests/identity/v3/test_idp.py11
-rw-r--r--functional/tests/identity/v3/test_project.py8
-rw-r--r--functional/tests/identity/v3/test_region.py11
-rw-r--r--functional/tests/identity/v3/test_role.py8
-rw-r--r--functional/tests/identity/v3/test_service.py13
-rw-r--r--functional/tests/identity/v3/test_service_provider.py11
-rw-r--r--functional/tests/identity/v3/test_token.py4
-rw-r--r--functional/tests/identity/v3/test_user.py6
-rw-r--r--functional/tests/image/v1/test_image.py10
-rw-r--r--functional/tests/image/v2/test_image.py12
-rw-r--r--functional/tests/network/v2/test_address_scope.py8
-rw-r--r--functional/tests/network/v2/test_floating_ip.py14
-rw-r--r--functional/tests/network/v2/test_ip_availability.py4
-rw-r--r--functional/tests/network/v2/test_network.py8
-rw-r--r--functional/tests/network/v2/test_network_agent.py41
-rw-r--r--functional/tests/network/v2/test_network_rbac.py69
-rw-r--r--functional/tests/network/v2/test_network_segment.py8
-rw-r--r--functional/tests/network/v2/test_port.py8
-rw-r--r--functional/tests/network/v2/test_router.py8
-rw-r--r--functional/tests/network/v2/test_security_group.py8
-rw-r--r--functional/tests/network/v2/test_security_group_rule.py8
-rw-r--r--functional/tests/network/v2/test_subnet.py8
-rw-r--r--functional/tests/network/v2/test_subnet_pool.py8
-rw-r--r--functional/tests/object/v1/test_container.py9
-rw-r--r--functional/tests/volume/v1/test_qos.py11
-rw-r--r--functional/tests/volume/v1/test_volume.py15
-rw-r--r--functional/tests/volume/v1/test_volume_type.py22
-rw-r--r--functional/tests/volume/v2/common.py23
-rw-r--r--functional/tests/volume/v2/test_qos.py15
-rw-r--r--functional/tests/volume/v2/test_snapshot.py (renamed from functional/tests/volume/v1/test_snapshot.py)15
-rw-r--r--functional/tests/volume/v2/test_volume.py31
-rw-r--r--functional/tests/volume/v2/test_volume_type.py26
-rw-r--r--openstackclient/api/api.py23
-rw-r--r--openstackclient/api/auth.py235
-rw-r--r--openstackclient/api/auth_plugin.py106
-rw-r--r--openstackclient/api/object_store_v1.py2
-rw-r--r--openstackclient/common/availability_zone.py37
-rw-r--r--openstackclient/common/client_config.py229
-rw-r--r--openstackclient/common/clientmanager.py263
-rw-r--r--openstackclient/common/command.py46
-rw-r--r--openstackclient/common/configuration.py17
-rw-r--r--openstackclient/common/exceptions.py108
-rw-r--r--openstackclient/common/extension.py42
-rw-r--r--openstackclient/common/limits.py23
-rw-r--r--openstackclient/common/logs.py188
-rw-r--r--openstackclient/common/module.py10
-rw-r--r--openstackclient/common/parseractions.py153
-rw-r--r--openstackclient/common/quota.py31
-rw-r--r--openstackclient/common/timing.py32
-rw-r--r--openstackclient/common/utils.py413
-rw-r--r--openstackclient/compute/client.py12
-rw-r--r--openstackclient/compute/v2/agent.py60
-rw-r--r--openstackclient/compute/v2/aggregate.py40
-rw-r--r--openstackclient/compute/v2/console.py50
-rw-r--r--openstackclient/compute/v2/fixedip.py38
-rw-r--r--openstackclient/compute/v2/flavor.py126
-rw-r--r--openstackclient/compute/v2/floatingip.py38
-rw-r--r--openstackclient/compute/v2/floatingippool.py36
-rw-r--r--openstackclient/compute/v2/host.py33
-rw-r--r--openstackclient/compute/v2/hypervisor.py7
-rw-r--r--openstackclient/compute/v2/hypervisor_stats.py3
-rw-r--r--openstackclient/compute/v2/keypair.py38
-rw-r--r--openstackclient/compute/v2/server.py289
-rw-r--r--openstackclient/compute/v2/server_backup.py6
-rw-r--r--openstackclient/compute/v2/server_group.py14
-rw-r--r--openstackclient/compute/v2/server_image.py113
-rw-r--r--openstackclient/compute/v2/service.py115
-rw-r--r--openstackclient/compute/v2/usage.py4
-rw-r--r--openstackclient/identity/client.py18
-rw-r--r--openstackclient/identity/common.py112
-rw-r--r--openstackclient/identity/v2_0/catalog.py41
-rw-r--r--openstackclient/identity/v2_0/ec2creds.py31
-rw-r--r--openstackclient/identity/v2_0/endpoint.py36
-rw-r--r--openstackclient/identity/v2_0/project.py43
-rw-r--r--openstackclient/identity/v2_0/role.py55
-rw-r--r--openstackclient/identity/v2_0/role_assignment.py114
-rw-r--r--openstackclient/identity/v2_0/service.py41
-rw-r--r--openstackclient/identity/v2_0/token.py21
-rw-r--r--openstackclient/identity/v2_0/user.py31
-rw-r--r--openstackclient/identity/v3/catalog.py41
-rw-r--r--openstackclient/identity/v3/consumer.py40
-rw-r--r--openstackclient/identity/v3/credential.py51
-rw-r--r--openstackclient/identity/v3/domain.py52
-rw-r--r--openstackclient/identity/v3/ec2creds.py29
-rw-r--r--openstackclient/identity/v3/endpoint.py48
-rw-r--r--openstackclient/identity/v3/federation_protocol.py37
-rw-r--r--openstackclient/identity/v3/group.py19
-rw-r--r--openstackclient/identity/v3/identity_provider.py49
-rw-r--r--openstackclient/identity/v3/mapping.py36
-rw-r--r--openstackclient/identity/v3/policy.py35
-rw-r--r--openstackclient/identity/v3/project.py32
-rw-r--r--openstackclient/identity/v3/region.py34
-rw-r--r--openstackclient/identity/v3/role.py143
-rw-r--r--openstackclient/identity/v3/role_assignment.py43
-rw-r--r--openstackclient/identity/v3/service.py46
-rw-r--r--openstackclient/identity/v3/service_provider.py49
-rw-r--r--openstackclient/identity/v3/token.py26
-rw-r--r--openstackclient/identity/v3/trust.py5
-rw-r--r--openstackclient/identity/v3/unscoped_saml.py7
-rw-r--r--openstackclient/identity/v3/user.py37
-rw-r--r--openstackclient/image/client.py15
-rw-r--r--openstackclient/image/v1/image.py36
-rw-r--r--openstackclient/image/v2/image.py86
-rw-r--r--openstackclient/network/client.py5
-rw-r--r--openstackclient/network/common.py25
-rw-r--r--openstackclient/network/v2/address_scope.py25
-rw-r--r--openstackclient/network/v2/floating_ip.py130
-rw-r--r--openstackclient/network/v2/floating_ip_pool.py66
-rw-r--r--openstackclient/network/v2/ip_availability.py13
-rw-r--r--openstackclient/network/v2/network.py48
-rw-r--r--openstackclient/network/v2/network_agent.py160
-rw-r--r--openstackclient/network/v2/network_rbac.py248
-rw-r--r--openstackclient/network/v2/network_segment.py5
-rw-r--r--openstackclient/network/v2/port.py134
-rw-r--r--openstackclient/network/v2/router.py80
-rw-r--r--openstackclient/network/v2/security_group.py21
-rw-r--r--openstackclient/network/v2/security_group_rule.py29
-rw-r--r--openstackclient/network/v2/subnet.py160
-rw-r--r--openstackclient/network/v2/subnet_pool.py81
-rw-r--r--openstackclient/object/client.py7
-rw-r--r--openstackclient/object/v1/account.py7
-rw-r--r--openstackclient/object/v1/container.py8
-rw-r--r--openstackclient/object/v1/object.py7
-rw-r--r--openstackclient/shell.py420
-rw-r--r--openstackclient/tests/api/fakes.py2
-rw-r--r--openstackclient/tests/api/test_api.py3
-rw-r--r--openstackclient/tests/api/test_image_v1.py2
-rw-r--r--openstackclient/tests/api/test_image_v2.py2
-rw-r--r--openstackclient/tests/api/test_object_store_v1.py2
-rw-r--r--openstackclient/tests/common/test_availability_zone.py1
-rw-r--r--openstackclient/tests/common/test_clientmanager.py368
-rw-r--r--openstackclient/tests/common/test_command.py3
-rw-r--r--openstackclient/tests/common/test_configuration.py17
-rw-r--r--openstackclient/tests/common/test_extension.py161
-rw-r--r--openstackclient/tests/common/test_logs.py15
-rw-r--r--openstackclient/tests/common/test_parseractions.py22
-rw-r--r--openstackclient/tests/common/test_quota.py7
-rw-r--r--openstackclient/tests/common/test_timing.py94
-rw-r--r--openstackclient/tests/common/test_utils.py400
-rw-r--r--openstackclient/tests/compute/v2/fakes.py253
-rw-r--r--openstackclient/tests/compute/v2/test_agent.py88
-rw-r--r--openstackclient/tests/compute/v2/test_aggregate.py65
-rw-r--r--openstackclient/tests/compute/v2/test_console.py196
-rw-r--r--openstackclient/tests/compute/v2/test_flavor.py259
-rw-r--r--openstackclient/tests/compute/v2/test_host.py118
-rw-r--r--openstackclient/tests/compute/v2/test_hypervisor.py3
-rw-r--r--openstackclient/tests/compute/v2/test_keypair.py62
-rw-r--r--openstackclient/tests/compute/v2/test_server.py413
-rw-r--r--openstackclient/tests/compute/v2/test_server_backup.py9
-rw-r--r--openstackclient/tests/compute/v2/test_server_group.py5
-rw-r--r--openstackclient/tests/compute/v2/test_server_image.py228
-rw-r--r--openstackclient/tests/compute/v2/test_service.py198
-rw-r--r--openstackclient/tests/fakes.py17
-rw-r--r--openstackclient/tests/identity/v2_0/fakes.py393
-rw-r--r--openstackclient/tests/identity/v2_0/test_catalog.py62
-rw-r--r--openstackclient/tests/identity/v2_0/test_endpoint.py159
-rw-r--r--openstackclient/tests/identity/v2_0/test_project.py264
-rw-r--r--openstackclient/tests/identity/v2_0/test_role.py297
-rw-r--r--openstackclient/tests/identity/v2_0/test_role_assignment.py271
-rw-r--r--openstackclient/tests/identity/v2_0/test_service.py148
-rw-r--r--openstackclient/tests/identity/v2_0/test_token.py44
-rw-r--r--openstackclient/tests/identity/v2_0/test_user.py295
-rw-r--r--openstackclient/tests/identity/v3/fakes.py378
-rw-r--r--openstackclient/tests/identity/v3/test_catalog.py19
-rw-r--r--openstackclient/tests/identity/v3/test_consumer.py2
-rw-r--r--openstackclient/tests/identity/v3/test_credential.py292
-rw-r--r--openstackclient/tests/identity/v3/test_domain.py152
-rw-r--r--openstackclient/tests/identity/v3/test_endpoint.py434
-rw-r--r--openstackclient/tests/identity/v3/test_group.py448
-rw-r--r--openstackclient/tests/identity/v3/test_identity_provider.py59
-rw-r--r--openstackclient/tests/identity/v3/test_mappings.py6
-rw-r--r--openstackclient/tests/identity/v3/test_project.py496
-rw-r--r--openstackclient/tests/identity/v3/test_protocol.py2
-rw-r--r--openstackclient/tests/identity/v3/test_region.py8
-rw-r--r--openstackclient/tests/identity/v3/test_role.py272
-rw-r--r--openstackclient/tests/identity/v3/test_role_assignment.py113
-rw-r--r--openstackclient/tests/identity/v3/test_service.py208
-rw-r--r--openstackclient/tests/identity/v3/test_service_provider.py29
-rw-r--r--openstackclient/tests/identity/v3/test_token.py49
-rw-r--r--openstackclient/tests/identity/v3/test_unscoped_saml.py3
-rw-r--r--openstackclient/tests/identity/v3/test_user.py404
-rw-r--r--openstackclient/tests/image/v1/test_image.py11
-rw-r--r--openstackclient/tests/image/v2/fakes.py40
-rw-r--r--openstackclient/tests/image/v2/test_image.py191
-rw-r--r--openstackclient/tests/network/v2/fakes.py287
-rw-r--r--openstackclient/tests/network/v2/test_address_scope.py58
-rw-r--r--openstackclient/tests/network/v2/test_floating_ip.py131
-rw-r--r--openstackclient/tests/network/v2/test_floating_ip_pool.py97
-rw-r--r--openstackclient/tests/network/v2/test_ip_availability.py36
-rw-r--r--openstackclient/tests/network/v2/test_network.py100
-rw-r--r--openstackclient/tests/network/v2/test_network_agent.py294
-rw-r--r--openstackclient/tests/network/v2/test_network_rbac.py430
-rw-r--r--openstackclient/tests/network/v2/test_network_segment.py3
-rw-r--r--openstackclient/tests/network/v2/test_port.py234
-rw-r--r--openstackclient/tests/network/v2/test_router.py137
-rw-r--r--openstackclient/tests/network/v2/test_security_group.py188
-rw-r--r--openstackclient/tests/network/v2/test_security_group_rule.py177
-rw-r--r--openstackclient/tests/network/v2/test_subnet.py338
-rw-r--r--openstackclient/tests/network/v2/test_subnet_pool.py161
-rw-r--r--openstackclient/tests/object/v1/fakes.py1
-rw-r--r--openstackclient/tests/test_shell.py590
-rw-r--r--openstackclient/tests/test_shell_integ.py668
-rw-r--r--openstackclient/tests/utils.py3
-rw-r--r--openstackclient/tests/volume/test_find_resource.py12
-rw-r--r--openstackclient/tests/volume/v1/fakes.py4
-rw-r--r--openstackclient/tests/volume/v1/test_qos_specs.py23
-rw-r--r--openstackclient/tests/volume/v1/test_volume.py70
-rw-r--r--openstackclient/tests/volume/v2/fakes.py369
-rw-r--r--openstackclient/tests/volume/v2/test_backup.py116
-rw-r--r--openstackclient/tests/volume/v2/test_qos_specs.py82
-rw-r--r--openstackclient/tests/volume/v2/test_snapshot.py109
-rw-r--r--openstackclient/tests/volume/v2/test_type.py160
-rw-r--r--openstackclient/tests/volume/v2/test_volume.py285
-rw-r--r--openstackclient/volume/client.py10
-rw-r--r--openstackclient/volume/v1/backup.py127
-rw-r--r--openstackclient/volume/v1/qos_specs.py14
-rw-r--r--openstackclient/volume/v1/service.py5
-rw-r--r--openstackclient/volume/v1/snapshot.py9
-rw-r--r--openstackclient/volume/v1/volume.py21
-rw-r--r--openstackclient/volume/v1/volume_transfer_request.py4
-rw-r--r--openstackclient/volume/v1/volume_type.py41
-rw-r--r--openstackclient/volume/v2/backup.py180
-rw-r--r--openstackclient/volume/v2/qos_specs.py38
-rw-r--r--openstackclient/volume/v2/service.py5
-rw-r--r--openstackclient/volume/v2/snapshot.py61
-rw-r--r--openstackclient/volume/v2/volume.py120
-rw-r--r--openstackclient/volume/v2/volume_transfer_request.py4
-rw-r--r--openstackclient/volume/v2/volume_type.py129
-rw-r--r--releasenotes/notes/add-port-unset-command-8bdaf1fa9c593374.yaml6
-rw-r--r--releasenotes/notes/add-server-group-quotas-b67fcba98619f0c9.yaml6
-rw-r--r--releasenotes/notes/bp-backup-snapshot-renamed-for-volume-resource-2d2d13ea8489a61f.yaml8
-rw-r--r--releasenotes/notes/bp-implement-network-agents-5eba48796318f094.yaml5
-rw-r--r--releasenotes/notes/bp-multi-argument-compute-0bc4522f6edca355.yaml5
-rw-r--r--releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml6
-rw-r--r--releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml17
-rw-r--r--releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml5
-rw-r--r--releasenotes/notes/bp-routed-networks-86a24f34d86fca53.yaml6
-rw-r--r--releasenotes/notes/bug-1535213-c91133586e07d71c.yaml6
-rw-r--r--releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml (renamed from openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml)1
-rw-r--r--releasenotes/notes/bug-1561599-d5f541f08ae6274a.yaml7
-rw-r--r--releasenotes/notes/bug-1575461-3fed33e53795684a.yaml5
-rw-r--r--releasenotes/notes/bug-1582774-3bba709ef61e33b7.yaml5
-rw-r--r--releasenotes/notes/bug-1588588-39927ef06ca35730.yaml6
-rw-r--r--releasenotes/notes/bug-1589332-2941f5286df7e5d4.yaml7
-rw-r--r--releasenotes/notes/bug-1589348-4a612a4efc7ed0e5.yaml5
-rw-r--r--releasenotes/notes/bug-1589935-8a56e6a18d836db9.yaml5
-rw-r--r--releasenotes/notes/bug-1592062-e327a31a5ae66809.yaml5
-rw-r--r--releasenotes/notes/bug-1592368-a4af69f163a1e208.yaml6
-rw-r--r--releasenotes/notes/bug-1592761-f45998453d6801f7.yaml5
-rw-r--r--releasenotes/notes/bug-1592906-a5604ec5abe77507.yaml5
-rw-r--r--releasenotes/notes/bug-1592906-ad67ce8736f3cd48.yaml8
-rw-r--r--releasenotes/notes/bug-1592906-e69b37377278d9c2.yaml5
-rw-r--r--releasenotes/notes/bug-1596443-9e2af267e91d1643.yaml6
-rw-r--r--releasenotes/notes/bug-1596798-b22fd587bdca8b36.yaml4
-rw-r--r--releasenotes/notes/bug-1596821-a07599eb4beb6342.yaml6
-rw-r--r--releasenotes/notes/bug-1597184-f4fb687b3d4d99d9.yaml5
-rw-r--r--releasenotes/notes/bug-1597188-a2ff62b809865365.yaml6
-rw-r--r--releasenotes/notes/bug-1597192-52801f7520287309.yaml4
-rw-r--r--releasenotes/notes/bug-1597198-e36b55f3fd185a3a.yaml5
-rw-r--r--releasenotes/notes/bug-1597296-9735f33eacf5552e.yaml5
-rw-r--r--releasenotes/notes/bug-1600196-6a733dd4e3371df7.yaml6
-rw-r--r--releasenotes/notes/bug-1602169-2750c9a6896d2825.yaml5
-rw-r--r--releasenotes/notes/bug-1605088-fea9347336764469.yaml4
-rw-r--r--releasenotes/notes/bug-1605475-84e649fb8c675737.yaml5
-rw-r--r--releasenotes/notes/bug-1605774-28aec51f6ec4926e.yaml4
-rw-r--r--releasenotes/notes/bug-1606105-ca06b230e22ab5c6.yaml4
-rw-r--r--releasenotes/notes/bug-1610883-e6345c32a35cc290.yaml7
-rw-r--r--releasenotes/notes/bug-1617384-55c88207115e2a5b.yaml5
-rw-r--r--releasenotes/notes/bug_1602073-5deb58deeafbc8be.yaml4
-rw-r--r--releasenotes/notes/flavor-create-with-project-19d41bfa93e3c6d0.yaml5
-rw-r--r--releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml24
-rw-r--r--releasenotes/notes/list_volume_type_access-f7d9aa6159f757ea.yaml10
-rw-r--r--releasenotes/notes/modify-compute-agent-set-77ff894ef62ebbc7.yaml3
-rw-r--r--releasenotes/notes/remove-osc_password-0767ac78267ef114.yaml7
-rw-r--r--releasenotes/notes/subnet-unset-5b458cdbaf93d766.yaml6
-rw-r--r--releasenotes/notes/unset-router-7b0cbd9518bb1de6.yaml7
-rw-r--r--releasenotes/notes/unset-subnet-pool-333052dd85b95653.yaml6
-rw-r--r--releasenotes/source/20_releases.rst6
-rw-r--r--releasenotes/source/conf.py2
-rw-r--r--releasenotes/source/index.rst25
-rw-r--r--releasenotes/source/mitaka.rst6
-rw-r--r--releasenotes/source/unreleased.rst1
-rw-r--r--requirements.txt15
-rw-r--r--setup.cfg57
-rw-r--r--test-requirements.txt29
-rwxr-xr-xtools/tox_install.sh55
-rw-r--r--tox.ini7
381 files changed, 16203 insertions, 7870 deletions
diff --git a/README.rst b/README.rst
index 340a0ea..8ebc24b 100644
--- a/README.rst
+++ b/README.rst
@@ -57,6 +57,15 @@ command::
openstack help
openstack help server create
+If you want to make changes to the OpenStackClient for testing and contribution,
+make any changes and then run::
+
+ python setup.py develop
+
+or::
+
+ pip install -e .
+
Configuration
=============
diff --git a/debian/changelog b/debian/changelog
index fed2a84..57e85e5 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,18 @@
+python-openstackclient (3.2.0-1) experimental; urgency=medium
+
+ [ gustavo panizzo ]
+ * Add autopkgtest tests
+
+ [ Corey Bryant ]
+ * New usptream release.
+ * Align (Build-)Depends with upstream.
+ * d/p/0001-reproducible-build.patch: Dropped. Fixed upstream.
+
+ [ Thomas Goirand ]
+ * Using OpenStack's Gerrit as VCS URLs.
+
+ -- Thomas Goirand <zigo@debian.org> Tue, 13 Sep 2016 18:57:24 +0200
+
python-openstackclient (2.6.0-1) experimental; urgency=medium
[ Corey Bryant ]
diff --git a/debian/control b/debian/control
index 12a1f0a..6394bf3 100644
--- a/debian/control
+++ b/debian/control
@@ -1,4 +1,5 @@
Source: python-openstackclient
+Testsuite: autopkgtest
Section: python
Priority: optional
Maintainer: PKG OpenStack <openstack-devel@lists.alioth.debian.org>
@@ -14,80 +15,89 @@ Build-Depends: debhelper (>= 9),
python3-all,
python3-pbr (>= 1.8),
python3-setuptools,
-Build-Depends-Indep: python-babel (>= 2.3.4),
+Build-Depends-Indep: python-aodhclient (>= 0.5.0),
+ python-babel (>= 2.3.4),
python-barbicanclient (>= 4.0.0),
python-cinderclient (>= 1:1.7.0),
python-cliff (>= 1.15.0),
- python-congressclient (>= 1.0.0),
+ python-congressclient (>= 1.3.0),
python-coverage,
python-crypto,
python-designateclient (>= 1.5.0),
python-fixtures (>= 3.0.0),
- python-glanceclient (>= 1:2.0.0),
+ python-glanceclient (>= 1:2.3.0),
python-hacking (>= 0.10.0),
- python-heatclient (>= 1.1.0),
- python-ironicclient (>= 1.1.0),
+ python-heatclient (>= 1.4.0),
+ python-ironicclient (>= 1.6.0),
python-ironic-inspector-client (>= 1.5.0),
python-keyring,
- python-keystoneauth1 (>= 2.1.0),
- python-keystoneclient (>= 1:1.7.0),
- python-mistralclient (>= 1.0.0),
+ python-keystoneauth1 (>= 2.10.0),
+ python-keystoneclient (>= 1:2.0.0),
+ python-mistralclient (>= 1:2.0.0),
python-muranoclient (>= 0.8.2),
python-mock (>= 1.3),
- python-neutronclient (>= 1:2.6.0),
+ python-neutronclient (>= 1:5.1.0),
python-novaclient (>= 2:2.30.0),
- python-openstacksdk (>= 0.8.6),
+ python-openstacksdk (>= 0.9.4),
python-os-client-config (>= 1.13.1),
+ python-osc-lib (>= 1.0.2),
python-os-testr (>= 0.7.0),
python-oslo.config (>= 1:3.10.0),
python-oslo.i18n (>= 2.1.0),
python-oslo.serialization (>= 1.2.0),
- python-oslo.utils (>= 3.11.0),
+ python-oslo.utils (>= 3.16.0),
python-oslosphinx (>= 2.5.0),
python-oslotest (>= 1.10.0),
- python-osprofiler (>= 1.3.0),
+ python-osprofiler (>= 1.4.0),
python-requests (>= 2.10.0),
- python-requests-mock (>= 0.7.0),
- python-saharaclient (>= 0.13.0),
+ python-requests-mock (>= 1.0),
+ python-saharaclient (>= 0.16.0),
python-senlinclient (>= 0.3.0),
python-six (>= 1.9.0),
- python-stevedore (>= 1.10.0),
+ python-stevedore (>= 1.16.0),
+ python-tempest (>= 1:12.1.0),
python-testtools (>= 1.4.0),
+ python-wrapt (>= 1.7.0),
python-zaqarclient (>= 1.0.0),
+ python3-aodhclient (>= 0.5.0),
python3-babel (>= 2.3.4),
python3-cinderclient (>= 1:1.7.0),
+ python3-congressclient (>= 1.3.0),
python3-cliff (>= 1.15.0),
python3-coverage,
python3-crypto,
python3-fixtures (>= 3.0.0),
- python3-glanceclient (>= 1:2.0.0),
- python3-ironicclient (>= 1.1.0),
+ python3-glanceclient (>= 1:2.3.0),
+ python3-heatclient (>= 1.4.0),
+ python3-ironicclient (>= 1.6.0),
python3-keyring,
- python3-keystoneauth1 (>= 2.1.0),
- python3-keystoneclient (>= 1:1.7.0),
- python3-mistralclient (>= 1.0.0),
+ python3-keystoneauth1 (>= 2.10.0),
+ python3-keystoneclient (>= 1:2.0.0),
+ python3-mistralclient (>= 1:2.0.0),
python3-mock (>= 1.3),
- python3-neutronclient (>= 1:2.6.0),
+ python3-neutronclient (>= 1:5.1.0),
python3-novaclient (>= 2:2.30.0),
- python3-openstacksdk (>= 0.8.6),
+ python3-openstacksdk (>= 0.9.4),
python3-os-client-config (>= 1.13.1),
python3-os-testr (>= 0.7.0),
+ python3-osc-lib (>= 1.0.2),
python3-oslo.config (>= 1:3.10.0),
python3-oslo.i18n (>= 2.1.0),
python3-oslo.serialization (>= 1.2.0),
- python3-oslo.utils (>= 3.11.0),
+ python3-oslo.utils (>= 3.16.0),
python3-oslotest (>= 1.10.0),
- python3-osprofiler (>= 1.3.0),
+ python3-osprofiler (>= 1.4.0),
python3-requests (>= 2.10.0),
- python3-requests-mock (>= 0.7.0),
- python3-saharaclient (>= 0.13.0),
+ python3-requests-mock (>= 1.0),
+ python3-saharaclient (>= 0.16.0),
python3-six (>= 1.9.0),
- python3-stevedore (>= 1.10.0),
+ python3-stevedore (>= 1.16.0),
python3-subunit,
- python3-tempest (>= 11.0.0),
+ python3-tempest (>= 1:12.1.0),
python3-testtools (>= 1.4.0),
+ python3-wrapt (>= 1.7.0),
subunit,
- tempest (>= 11.0.0),
+ tempest (>= 1:12.1.0),
testrepository,
# python-searchlightclient (>= 0.2.0), Not packaged yet: TODO
Standards-Version: 3.9.8
@@ -101,22 +111,23 @@ Depends: python-babel (>= 2.3.4),
python-cinderclient (>= 1:1.7.0),
python-cliff (>= 1.15.0),
python-crypto,
- python-glanceclient (>= 1:2.0.0),
+ python-glanceclient (>= 1:2.3.0),
python-keyring,
- python-keystoneauth1 (>= 2.1.0),
- python-keystoneclient (>= 1:1.7.0),
- python-neutronclient (>= 1:2.6.0),
+ python-keystoneauth1 (>= 2.10.0),
+ python-keystoneclient (>= 1:2.0.0),
+ python-neutronclient (>= 1:5.1.0),
python-novaclient (>= 2:2.30.0),
- python-openstacksdk (>= 0.8.6),
+ python-openstacksdk (>= 0.9.4),
python-os-client-config (>= 1.13.1),
+ python-osc-lib (>= 1.0.2),
python-oslo.config (>= 1:3.10.0),
python-oslo.i18n (>= 2.1.0),
python-oslo.serialization (>= 1.2.0),
- python-oslo.utils (>= 3.11.0),
+ python-oslo.utils (>= 3.16.0),
python-pbr (>= 1.8),
python-requests (>= 2.10.0),
python-six (>= 1.9.0),
- python-stevedore (>= 1.10.0),
+ python-stevedore (>= 1.16.0),
${misc:Depends},
${python:Depends},
${sphinxdoc:Depends},
@@ -139,22 +150,23 @@ Depends: python3-babel (>= 2.3.4),
python3-cinderclient (>= 1:1.7.0),
python3-cliff (>= 1.15.0),
python3-crypto,
- python3-glanceclient (>= 1:2.0.0),
+ python3-glanceclient (>= 1:2.3.0),
python3-keyring,
- python3-keystoneauth1 (>= 2.1.0),
- python3-keystoneclient (>= 1:1.7.0),
- python3-neutronclient (>= 1:2.6.0),
+ python3-keystoneauth1 (>= 2.10.0),
+ python3-keystoneclient (>= 1:2.0.0),
+ python3-neutronclient (>= 1:5.1.0),
python3-novaclient (>= 2:2.30.0),
- python3-openstacksdk (>= 0.8.6),
+ python3-openstacksdk (>= 0.9.4),
python3-os-client-config (>= 1.13.1),
+ python3-osc-lib (>= 1.0.2),
python3-oslo.config (>= 1:3.10.0),
python3-oslo.i18n (>= 2.1.0),
python3-oslo.serialization (>= 1.2.0),
- python3-oslo.utils (>= 3.11.0),
+ python3-oslo.utils (>= 3.16.0),
python3-pbr (>= 1.8),
python3-requests (>= 2.10.0),
python3-six (>= 1.9.0),
- python3-stevedore (>= 1.10.0),
+ python3-stevedore (>= 1.16.0),
${misc:Depends},
${python3:Depends},
Recommends: ${python3:Recommends},
diff --git a/debian/patches/0001-reproducible-build.patch b/debian/patches/0001-reproducible-build.patch
deleted file mode 100644
index 9c6907e..0000000
--- a/debian/patches/0001-reproducible-build.patch
+++ /dev/null
@@ -1,16 +0,0 @@
-Description: Make doc build reproducible
-Author: Chriss Lamb <lamby@debian.org>
-Forwarded: https://review.openstack.org/#/c/340354/
-Last-Update: 2016-07-11
-
---- python-openstackclient-2.6.0.orig/doc/source/conf.py
-+++ python-openstackclient-2.6.0/doc/source/conf.py
-@@ -80,7 +80,7 @@ release = version_info.release_string()
-
- # List of patterns, relative to source directory, that match files and
- # directories to ignore when looking for source files.
--exclude_patterns = []
-+exclude_patterns = ['**tests**']
-
- # The reST default role (used for this markup: `text`) to use for all
- # documents.
diff --git a/debian/patches/series b/debian/patches/series
deleted file mode 100644
index 91b59f0..0000000
--- a/debian/patches/series
+++ /dev/null
@@ -1 +0,0 @@
-0001-reproducible-build.patch
diff --git a/debian/tests/control b/debian/tests/control
new file mode 100644
index 0000000..0f1b54a
--- /dev/null
+++ b/debian/tests/control
@@ -0,0 +1,3 @@
+Tests: openstackclient
+Depends: @, openstack-deploy (>= 0.14), debconf, apt-utils
+Restrictions: needs-root, breaks-testbed, isolation-container, allow-stderr
diff --git a/debian/tests/openstackclient b/debian/tests/openstackclient
new file mode 100755
index 0000000..82242e1
--- /dev/null
+++ b/debian/tests/openstackclient
@@ -0,0 +1,45 @@
+#!/bin/bash
+set -e
+
+openstack-deploy --non-interactive controller
+cp openrc.sh /root/
+cp osinstallrc /root/
+
+source /root/openrc.sh
+
+# keystone
+openstack user list
+openstack role list
+openstack user create test
+openstack user list | grep test
+openstack user delete test
+openstack user list | grep -v test
+openstack service list
+openstack endpoint list
+openstack catalog list
+
+# glance
+openstack image list
+openstack image create --file /bin/bash bash
+openstack image list |grep bash
+openstack image delete bash
+openstack image list |grep -v bash
+
+# nova
+openstack server list
+openstack host list
+openstack flavor list
+openstack flavor create test
+openstack flavor list |grep test
+openstack flavor delete test
+openstack flavor list |grep -v test
+openstack hypervisor list
+
+# neutron
+openstack security group list
+openstack security group create test
+openstack security group list | grep test
+openstack security group delete test
+openstack security group list | grep -v test
+
+
diff --git a/doc/source/authentication.rst b/doc/source/authentication.rst
index a3986ee..be16bd7 100644
--- a/doc/source/authentication.rst
+++ b/doc/source/authentication.rst
@@ -130,7 +130,7 @@ If using a domain as authorization scope, set either it's name or ID.
* ``--os-domain-id`` or ``OS_DOMAIN_ID``
Note that if the user and project share the same domain, then simply setting
-``os-default-domain`` or ``OS_DEFAULT_DOMAIN`` is sufficient.
+``--os-default-domain`` or ``OS_DEFAULT_DOMAIN`` to the domain ID is sufficient.
Thus, a minimal set of environment variables would be:
diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst
index 00b314a..4f38aa5 100644
--- a/doc/source/backwards-incompatible.rst
+++ b/doc/source/backwards-incompatible.rst
@@ -13,8 +13,25 @@ deprecation warnings that indicate the new commands (or options) to use.
Commands labeled as a beta according to :doc:`command-beta` are exempt from
this backwards incompatible change handling.
-List of Backwards Incompatible Changes
-======================================
+Backwards Incompatible Changes
+==============================
+
+Release 3.0
+-----------
+
+1. Remove the ``osc_password`` authentication plugin.
+
+ This was the 'last-resort' plugin default that worked around an old default
+ Keystone configuration for the ``admin_endpoint`` and ``public_endpoint``.
+
+ * In favor of: ``password``
+ * As of: 3.0
+ * Removed in: n/a
+ * Bug: n/a
+ * Commit: https://review.openstack.org/332938
+
+Releases Before 3.0
+-------------------
1. Rename command `openstack project usage list`
@@ -107,7 +124,7 @@ List of Backwards Incompatible Changes
8. `region` commands no longer support `url`
- The Keystone team removed support for thr `url` attribute from the client
+ The Keystone team removed support for the `url` attribute from the client
and server side. Changes to the `create`, `set` and `list` commands for
regions have been affected.
@@ -153,6 +170,19 @@ List of Backwards Incompatible Changes
* Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065
* Commit: https://review.openstack.org/#/c/281088/
+12. <version> <url> <md5hash> should be optional for command `openstack compute agent set`
+
+ Previously, the command was `openstack compute agent set <id> <version> <url> <md5hash>`,
+ whereas now it is: `openstack compute agent set <id> --version <version>
+ --url <url>
+ --md5hash <md5hash>`.
+
+ * In favor of: making <version> <url> <md5hash> optional.
+ * As of: NA
+ * Removed in: NA
+ * Bug: NA
+ * Commit: https://review.openstack.org/#/c/328819/
+
13. `aggregate set` commands will no longer return the modified resource
Previously, modifying an aggregate would result in the new aggregate being
@@ -167,27 +197,41 @@ List of Backwards Incompatible Changes
14. Output of `ip floating list` command has changed.
- When using Compute v2, the original output of `ip floating list` command is:
- +----+--------+------------+----------+-------------+
- | ID | Pool | IP | Fixed IP | Instance ID |
- +----+--------+-----------------------+-------------+
- | 1 | public | 172.24.4.1 | None | None |
- +----+--------+------------+----------+-------------+
+ When using Compute v2, the original output is:
+
+ .. code-block:: bash
+
+ # ip floating list
+
+ +----+--------+------------+----------+-------------+
+ | ID | Pool | IP | Fixed IP | Instance ID |
+ +----+--------+-----------------------+-------------+
+ | 1 | public | 172.24.4.1 | None | None |
+ +----+--------+------------+----------+-------------+
Now it changes to:
- +----+---------------------+------------------+-----------+--------+
- | ID | Floating IP Address | Fixed IP Address | Server ID | Pool |
- +----+---------------------+------------------+-----------+--------+
- | 1 | 172.24.4.1 | None | None | public |
- +----+---------------------+------------------+-----------+--------+
-
- When using Network v2, the output of `ip floating list` command is:
- +--------------------------------------+---------------------+------------------+------+
- | ID | Floating IP Address | Fixed IP Address | Port |
- +--------------------------------------+---------------------+------------------+------+
- | 1976df86-e66a-4f96-81bd-c6ffee6407f1 | 172.24.4.3 | None | None |
- +--------------------------------------+---------------------+------------------+------+
- which is different from Compute v2.
+
+ .. code-block:: bash
+
+ # ip floating list
+
+ +----+---------------------+------------------+-----------+--------+
+ | ID | Floating IP Address | Fixed IP Address | Server ID | Pool |
+ +----+---------------------+------------------+-----------+--------+
+ | 1 | 172.24.4.1 | None | None | public |
+ +----+---------------------+------------------+-----------+--------+
+
+ When using Network v2, which is different from Compute v2. The output is:
+
+ .. code-block:: bash
+
+ # ip floating list
+
+ +--------------------------------------+---------------------+------------------+------+
+ | ID | Floating IP Address | Fixed IP Address | Port |
+ +--------------------------------------+---------------------+------------------+------+
+ | 1976df86-e66a-4f96-81bd-c6ffee6407f1 | 172.24.4.3 | None | None |
+ +--------------------------------------+---------------------+------------------+------+
* In favor of: Use `ip floating list` command
* As of: NA
diff --git a/doc/source/command-beta.rst b/doc/source/command-beta.rst
index f0a4f85..bc8c04c 100644
--- a/doc/source/command-beta.rst
+++ b/doc/source/command-beta.rst
@@ -12,23 +12,29 @@ To address these challenges, an OpenStackClient command may
be labeled as a beta command according to the guidelines
below. Such commands may introduce backwards incompatible
changes and may use REST API enhancements not yet released.
+This also applies to command options associated with the beta
+command object.
-See the examples below on how to label a command as a beta
-by updating the command documentation, help and implementation.
+See the examples below on how to label an entire command or
+a specific option as a beta by updating the documentation
+and implementation.
-The initial release note must label the new command as a beta.
-No further release notes are required until the command
-is no longer a beta. At which time, the command beta label
-or the command itself must be removed and a new release note
+The initial release note must label the new command or option
+as a beta. No further release notes are required until the command
+or option is no longer a beta. At which time, the beta label or
+the command or option itself must be removed and a new release note
must be provided.
+Beta Command Example
+--------------------
+
Documentation
--------------
+~~~~~~~~~~~~~
The command documentation must label the command as a beta.
example list
-~~~~~~~~~~~~
+++++++++++++
List examples
@@ -42,7 +48,7 @@ List examples
os example list
Help
-----
+~~~~
The command help must label the command as a beta.
@@ -57,7 +63,7 @@ The command help must label the command as a beta.
"""
Implementation
---------------
+~~~~~~~~~~~~~~
The command must raise a ``CommandError`` exception if beta commands
are not enabled via ``--os-beta-command`` global option.
@@ -66,3 +72,35 @@ are not enabled via ``--os-beta-command`` global option.
def take_action(self, parsed_args):
self.validate_os_beta_command_enabled()
+
+Beta Option Example
+-------------------
+
+Documentation
+~~~~~~~~~~~~~
+
+The option documentation must label the option as a beta.
+
+.. option:: --example <example>
+
+ Example
+
+ .. caution:: This is a beta command option and subject
+ to change. Use global option ``--os-beta-command``
+ to enable this command option.
+
+Implementation
+~~~~~~~~~~~~~~
+
+The option must not be added if beta commands are not
+enabled via ``--os-beta-command`` global option.
+
+.. code-block:: python
+
+ def get_parser(self, prog_name):
+ if self.app.options.os_beta_command:
+ parser.add_argument(
+ '--example',
+ metavar='<example>',
+ help=_("Example")
+ )
diff --git a/doc/source/command-errors.rst b/doc/source/command-errors.rst
index a24dc31..c4adb7d 100644
--- a/doc/source/command-errors.rst
+++ b/doc/source/command-errors.rst
@@ -51,7 +51,7 @@ raised that includes the name of the file that was attempted to be opened.
) as p:
public_key = p.read()
except IOError as e:
- msg = "Key file %s not found: %s"
+ msg = _("Key file %s not found: %s")
raise exceptions.CommandError(
msg % (parsed_args.public_key, e),
)
@@ -135,7 +135,7 @@ remaining arguments are set using the ``update()`` method.
parsed_args.property,
)
except SomeException: # Need to define the exceptions to catch here
- self.app.log.error("Property set failed")
+ LOG.error(_("Property set failed"))
result += 1
if parsed_args.state:
@@ -145,7 +145,7 @@ remaining arguments are set using the ``update()`` method.
parsed_args.state,
)
except SomeException: # Need to define the exceptions to catch here
- self.app.log.error("State set failed")
+ LOG.error(_("State set failed"))
result += 1
try:
@@ -154,7 +154,7 @@ remaining arguments are set using the ``update()`` method.
**kwargs
)
except SomeException: # Need to define the exceptions to catch here
- self.app.log.error("Update failed")
+ LOG.error(_("Update failed"))
result += 1
# NOTE(dtroyer): We need to signal the error, and a non-zero return code,
@@ -166,8 +166,8 @@ Example 2
~~~~~~~~~
This example is taken from the ``network delete`` command which takes multiple
-networks to delete. All networks will be delete in a loop, which makes multiple
-``delete_network()`` calls.
+networks to delete. All networks will be deleted in a loop, which makes
+multiple ``delete_network()`` calls.
.. code-block:: python
@@ -179,7 +179,7 @@ networks to delete. All networks will be delete in a loop, which makes multiple
'network',
metavar="<network>",
nargs="+",
- help=("Network(s) to delete (name or ID)")
+ help=_("Network(s) to delete (name or ID)")
)
return parser
@@ -191,11 +191,12 @@ networks to delete. All networks will be delete in a loop, which makes multiple
obj = client.find_network(network, ignore_missing=False)
client.delete_network(obj)
except Exception:
- self.app.log.error("Failed to delete network with name "
- "or ID %s." % network)
+ LOG.error(_("Failed to delete network with name "
+ "or ID %s."), network)
ret += 1
if ret > 0:
total = len(parsed_args.network)
- msg = "Failed to delete %s of %s networks." % (ret, total)
+ msg = (_("Failed to delete %(ret)s of %(total)s networks.")
+ % {"ret": ret, "total": total})
raise exceptions.CommandError(msg)
diff --git a/doc/source/command-logs.rst b/doc/source/command-logs.rst
new file mode 100644
index 0000000..6212651
--- /dev/null
+++ b/doc/source/command-logs.rst
@@ -0,0 +1,73 @@
+============
+Command Logs
+============
+
+Logger usage in OpenStackClient is not exactly the same as those in other
+OpenStack projects. The following basic rules should be followed.
+
+1. OpenStackClient uses python standard logging library instead of oslo.log
+ so that it will depend on oslo as little as possible.
+
+2. All logs except debug log need to be translated. The log message strings
+ that need to be translated should follow the rule of i18n guidelines:
+ http://docs.openstack.org/developer/oslo.i18n/guidelines.html
+
+3. There are mainly two kinds of logs in OpenStackClient: command specific
+ log and general log. Use different logger to record them. The examples
+ below will show the detail.
+
+Command specific log
+====================
+
+Command specific logs are those messages that used to record info, warning
+and error generated from a specific command. OpenStackClient uses the logger
+of the module the command belongs to to record the command specific logs.
+
+Example
+~~~~~~~
+
+This example shows how to log command specific logs in OpenStackClient.
+
+.. code-block:: python
+
+ import logging
+
+ from openstackclient.i18n import _
+
+
+ LOG = logging.getLogger(__name__) # Get the logger of this module
+
+ ## ...
+
+ LOG.error(_("Error message"))
+ LOG.warning(_("Warning message"))
+ LOG.info(_("Info message"))
+ LOG.debug("Debug message") # Debug messages do not need to be translated
+
+ ## ...
+
+General log
+===========
+
+General logs are those messages that not specific to any single command. Use
+the logger of ``openstackclient.shell`` to record them. In each command class,
+we can simply get this logger by ``self.app.log``.
+
+Example
+~~~~~~~
+
+This example shows how to log general logs in OpenStackClient.
+
+.. code-block:: python
+
+ from openstackclient.i18n import _
+
+
+ ## ...
+
+ self.app.log.error(_("Error message"))
+ self.app.log.warning(_("Warning message"))
+ self.app.log.info(_("Info message"))
+ self.app.log.debug("Debug message") # Debug messages do not need to be translated
+
+ ## ...
diff --git a/doc/source/command-objects/aggregate.rst b/doc/source/command-objects/aggregate.rst
index 6c4baa5..25c7041 100644
--- a/doc/source/command-objects/aggregate.rst
+++ b/doc/source/command-objects/aggregate.rst
@@ -38,7 +38,7 @@ Create a new aggregate
os aggregate create
[--zone <availability-zone>]
- [--property <key=value>]
+ [--property <key=value> [...] ]
<name>
.. option:: --zone <availability-zone>
@@ -56,17 +56,17 @@ Create a new aggregate
aggregate delete
----------------
-Delete an existing aggregate
+Delete existing aggregate(s)
.. program:: aggregate delete
.. code:: bash
os aggregate delete
- <aggregate>
+ <aggregate> [<aggregate> ...]
.. describe:: <aggregate>
- Aggregate to delete (name or ID)
+ Aggregate(s) to delete (name or ID)
aggregate list
--------------
@@ -115,7 +115,7 @@ Set aggregate properties
os aggregate set
[--name <new-name>]
[--zone <availability-zone>]
- [--property <key=value>]
+ [--property <key=value> [...] ]
<aggregate>
.. option:: --name <name>
@@ -160,8 +160,7 @@ Unset aggregate properties
.. code-block:: bash
os aggregate unset
- --property <key>
- [--property <key>] ...
+ [--property <key> [...] ]
<aggregate>
.. option:: --property <key>
diff --git a/doc/source/command-objects/backup.rst b/doc/source/command-objects/backup.rst
index 9d7fb95..ac88708 100644
--- a/doc/source/command-objects/backup.rst
+++ b/doc/source/command-objects/backup.rst
@@ -2,12 +2,13 @@
backup
======
-Block Storage v1
+Block Storage v1, v2
backup create
-------------
Create new backup
+(Deprecated, please use ``volume backup create`` instead)
.. program:: backup create
.. code:: bash
@@ -16,6 +17,9 @@ Create new backup
[--container <container>]
[--name <name>]
[--description <description>]
+ [--snapshot <snapshot>]
+ [--force]
+ [--incremental]
<volume>
.. option:: --container <container>
@@ -30,6 +34,24 @@ Create new backup
Description of the backup
+.. option:: --snapshot <snapshot>
+
+ Snapshot to backup (name or ID)
+
+ *Volume version 2 only*
+
+.. option:: --force
+
+ Allow to back up an in-use volume
+
+ *Volume version 2 only*
+
+.. option:: --incremental
+
+ Perform an incremental backup
+
+ *Volume version 2 only*
+
.. _backup_create-backup:
.. describe:: <volume>
@@ -39,22 +61,31 @@ backup delete
-------------
Delete backup(s)
+(Deprecated, please use ``volume backup delete`` instead)
.. program:: backup delete
.. code:: bash
os backup delete
+ [--force]
<backup> [<backup> ...]
+.. option:: --force
+
+ Allow delete in state other than error or available
+
+ *Volume version 2 only*
+
.. _backup_delete-backup:
.. describe:: <backup>
- Backup(s) to delete (ID only)
+ Backup(s) to delete (name or ID)
backup list
-----------
List backups
+(Deprecated, please use ``volume backup list`` instead)
.. program:: backup list
.. code:: bash
@@ -70,6 +101,7 @@ backup restore
--------------
Restore backup
+(Deprecated, please use ``volume backup restore`` instead)
.. program:: backup restore
.. code:: bash
@@ -81,7 +113,7 @@ Restore backup
.. _backup_restore-backup:
.. describe:: <backup>
- Backup to restore (ID only)
+ Backup to restore (name or ID)
.. describe:: <volume>
@@ -91,6 +123,7 @@ backup show
-----------
Display backup details
+(Deprecated, please use ``volume backup show`` instead)
.. program:: backup show
.. code:: bash
@@ -101,4 +134,4 @@ Display backup details
.. _backup_show-backup:
.. describe:: <backup>
- Backup to display (ID only)
+ Backup to display (name or ID)
diff --git a/doc/source/command-objects/compute-agent.rst b/doc/source/command-objects/compute-agent.rst
index b00a981..239a24e 100644
--- a/doc/source/command-objects/compute-agent.rst
+++ b/doc/source/command-objects/compute-agent.rst
@@ -59,42 +59,44 @@ Delete compute agent(s)
compute agent list
------------------
-List compute agent command
+List compute agents
.. program:: compute agent list
.. code:: bash
os compute agent list [--hypervisor <hypervisor>]
-.. _compute_agent-list:
-.. describe:: --hypervisor <hypervisor>
+.. option:: --hypervisor <hypervisor>
- Optional type of hypervisor
+ Type of hypervisor
compute agent set
-----------------
-Set compute agent command
+Set compute agent properties
.. program:: agent set
.. code:: bash
os compute agent set
- <id> <version> <url> <md5hash>
+ [--agent-version <version>]
+ [--url <url]
+ [--md5hash <md5hash>]
+ <id>
.. _compute_agent-set:
-.. describe:: <id>
+.. option:: --agent-version <version>
- ID of the agent
+ Version of the agent
-.. describe:: <version>
+.. option:: --url <url>
- Version of the agent
+ URL of the agent
-.. describe:: <url>
+.. option:: --md5hash <md5hash>
- URL
+ MD5 hash of the agent
-.. describe:: <md5hash>
+.. describe:: <id>
- MD5 hash
+ Agent to modify (ID only)
diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst
index 71225bc..57dd2ba 100644
--- a/doc/source/command-objects/compute-service.rst
+++ b/doc/source/command-objects/compute-service.rst
@@ -7,23 +7,23 @@ Compute v2
compute service delete
----------------------
-Delete service command
+Delete compute service(s)
.. program:: compute service delete
.. code:: bash
os compute service delete
- <service>
+ <service> [<service> ...]
.. _compute-service-delete:
.. describe:: <service>
- Compute service to delete (ID only)
+ Compute service(s) to delete (ID only)
compute service list
--------------------
-List service command
+List compute services
.. program:: compute service list
.. code:: bash
@@ -50,7 +50,7 @@ List service command
compute service set
-------------------
-Set service command
+Set compute service properties
.. program:: compute service set
.. code:: bash
@@ -58,6 +58,7 @@ Set service command
os compute service set
[--enable | --disable]
[--disable-reason <reason>]
+ [--up | --down]
<host> <service>
.. _compute-service-set:
@@ -73,11 +74,19 @@ Set service command
Reason for disabling the service (in quotes). Should be used with --disable option.
+.. option:: --up
+
+ Force up service
+
+.. option:: --down
+
+ Force down service
+
.. describe:: <host>
Name of host
.. describe:: <service>
- Name of service
+ Name of service (Binary name)
diff --git a/doc/source/command-objects/console-url.rst b/doc/source/command-objects/console-url.rst
index 9cab879..166d0a9 100644
--- a/doc/source/command-objects/console-url.rst
+++ b/doc/source/command-objects/console-url.rst
@@ -24,12 +24,24 @@ Show server's remote console URL
.. option:: --xvpvnc
- Show xpvnc console URL
+ Show xvpvnc console URL
.. option:: --spice
Show SPICE console URL
+.. option:: --rdp
+
+ Show RDP console URL
+
+.. option:: --serial
+
+ Show serial console URL
+
+.. option:: --mks
+
+ Show WebMKS console URL
+
.. describe:: <server>
Server to show URL (name or ID)
diff --git a/doc/source/command-objects/consumer.rst b/doc/source/command-objects/consumer.rst
index 91294fa..335eaea 100644
--- a/doc/source/command-objects/consumer.rst
+++ b/doc/source/command-objects/consumer.rst
@@ -24,17 +24,17 @@ Create new consumer
consumer delete
---------------
-Delete consumer
+Delete consumer(s)
.. program:: consumer delete
.. code:: bash
os consumer delete
- <consumer>
+ <consumer> [<consumer> ...]
.. describe:: <consumer>
- Consumer to delete
+ Consumer(s) to delete
consumer list
-------------
diff --git a/doc/source/command-objects/credential.rst b/doc/source/command-objects/credential.rst
index ed8a00d..538362a 100644
--- a/doc/source/command-objects/credential.rst
+++ b/doc/source/command-objects/credential.rst
@@ -1,27 +1,113 @@
-===========
+==========
credential
-===========
+==========
Identity v3
credential create
-------------------
+-----------------
-.. ''[consider rolling the ec2 creds into this too]''
+Create new credential
+.. program:: credential create
.. code:: bash
os credential create
- --x509
- [<private-key-file>]
- [<certificate-file>]
+ [--type <type>]
+ [--project <project>]
+ <user> <data>
+
+.. option:: --type <type>
+
+ New credential type
+
+.. option:: --project <project>
+
+ Project which limits the scope of the credential (name or ID)
+
+.. _credential_create:
+.. describe:: <user>
+
+ User that owns the credential (name or ID)
+
+.. describe:: <data>
+
+ New credential data
+
+credential delete
+-----------------
+
+Delete credential(s)
+
+.. program:: credential delete
+.. code:: bash
+
+ os credential delete
+ <credential-id> [<credential-id> ...]
+
+.. _credential_delete:
+.. describe:: <credential-id>
+
+ ID(s) of credential to delete
+
+credential list
+---------------
+
+List credentials
+
+.. program:: credential list
+.. code:: bash
+
+ os credential list
+
+credential set
+--------------
+
+Set credential properties
+
+.. program:: credential set
+.. code:: bash
+
+ os credential set
+ [--user <user>]
+ [--type <type>]
+ [--data <data>]
+ [--project <project>]
+ <credential-id>
+
+.. option:: --user <user>
+
+ User that owns the credential (name or ID)
+
+.. option:: --type <type>
+
+ New credential type
+
+.. option:: --data <data>
+
+ New credential data
+
+.. option:: --project <project>
+
+ Project which limits the scope of the credential (name or ID)
+
+.. _credential_set:
+.. describe:: <credential-id>
+
+ ID of credential to change
credential show
-----------------
+---------------
+
+Display credential details
+.. program:: credential show
.. code:: bash
os credential show
- [--token]
- [--user]
- [--x509 [--root]]
+ <credential-id>
+
+.. _credential_show:
+.. describe:: <credential-id>
+
+ ID of credential to display
diff --git a/doc/source/command-objects/domain.rst b/doc/source/command-objects/domain.rst
index 9447357..573a78c 100644
--- a/doc/source/command-objects/domain.rst
+++ b/doc/source/command-objects/domain.rst
@@ -43,17 +43,17 @@ Create new domain
domain delete
-------------
-Delete domain
+Delete domain(s)
.. program:: domain delete
.. code:: bash
os domain delete
- <domain>
+ <domain> [<domain> ...]
.. describe:: <domain>
- Domain to delete (name or ID)
+ Domain(s) to delete (name or ID)
domain list
-----------
diff --git a/doc/source/command-objects/ec2-credentials.rst b/doc/source/command-objects/ec2-credentials.rst
index 6748422..caf7fd6 100644
--- a/doc/source/command-objects/ec2-credentials.rst
+++ b/doc/source/command-objects/ec2-credentials.rst
@@ -55,7 +55,7 @@ Delete EC2 credentials
os ec2 credentials delete
[--user <user>]
[--user-domain <user-domain>]
- <access-key>
+ <access-key> [<access-key> ...]
.. option:: --user <user>
@@ -71,7 +71,7 @@ Delete EC2 credentials
.. _ec2_credentials_delete-access-key:
.. describe:: access-key
- Credentials access key
+ Credentials access key(s)
The :option:`--user` option is typically only useful for admin users, but
may be allowed for other users depending on the policy of the cloud and
diff --git a/doc/source/command-objects/endpoint.rst b/doc/source/command-objects/endpoint.rst
index 9872a58..c058c84 100644
--- a/doc/source/command-objects/endpoint.rst
+++ b/doc/source/command-objects/endpoint.rst
@@ -40,7 +40,7 @@ Create new endpoint
.. _endpoint_create-endpoint:
.. describe:: <service>
- New endpoint service (name or ID)
+ Service to be associated with new endpoint (name or ID)
*Identity version 3 only*
@@ -68,7 +68,7 @@ Create new endpoint
.. describe:: <service>
- New endpoint service (name or ID)
+ Service to be associated with new endpoint(name or ID)
.. describe:: <interface>
@@ -81,18 +81,18 @@ Create new endpoint
endpoint delete
---------------
-Delete endpoint
+Delete endpoint(s)
.. program:: endpoint delete
.. code:: bash
os endpoint delete
- <endpoint-id>
+ <endpoint-id> [<endpoint-id> ...]
.. _endpoint_delete-endpoint:
.. describe:: <endpoint-id>
- Endpoint ID to delete
+ Endpoint(s) to delete (ID only)
endpoint list
-------------
@@ -110,7 +110,7 @@ List endpoints
.. option:: --service <service>
- Filter by service
+ Filter by service (name or ID)
*Identity version 3 only*
@@ -177,7 +177,7 @@ Set endpoint properties
.. _endpoint_set-endpoint:
.. describe:: <endpoint-id>
- Endpoint ID to modify
+ Endpoint to modify (ID only)
endpoint show
-------------
diff --git a/doc/source/command-objects/federation-protocol.rst b/doc/source/command-objects/federation-protocol.rst
index 5b4ea48..911d4e9 100644
--- a/doc/source/command-objects/federation-protocol.rst
+++ b/doc/source/command-objects/federation-protocol.rst
@@ -34,14 +34,14 @@ Create new federation protocol
federation protocol delete
--------------------------
-Delete federation protocol
+Delete federation protocol(s)
.. program:: federation protocol delete
.. code:: bash
os federation protocol delete
--identity-provider <identity-provider>
- <federation-protocol>
+ <federation-protocol> [<federation-protocol> ...]
.. option:: --identity-provider <identity-provider>
@@ -49,7 +49,7 @@ Delete federation protocol
.. describe:: <federation-protocol>
- Federation protocol to delete (name or ID)
+ Federation protocol(s) to delete (name or ID)
federation protocol list
------------------------
diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst
index a30bede..9ea504c 100644
--- a/doc/source/command-objects/flavor.rst
+++ b/doc/source/command-objects/flavor.rst
@@ -21,6 +21,9 @@ Create new flavor
[--vcpus <num-cpu>]
[--rxtx-factor <factor>]
[--public | --private]
+ [--property <key=value> [...] ]
+ [--project <project>]
+ [--project-domain <project-domain>]
<flavor-name>
.. option:: --id <id>
@@ -59,6 +62,20 @@ Create new flavor
Flavor is not available to other projects
+.. option:: --property <key=value>
+
+ Property to add for this flavor (repeat option to set multiple properties)
+
+.. option:: --project <project>
+
+ Allow <project> to access private flavor (name or ID)
+ (Must be used with :option:`--private` option)
+
+.. option:: --project-domain <project-domain>
+
+ Domain the project belongs to (name or ID).
+ This can be used in case collisions between project names exist.
+
.. _flavor_create-flavor-name:
.. describe:: <flavor-name>
@@ -67,18 +84,18 @@ Create new flavor
flavor delete
-------------
-Delete flavor
+Delete flavor(s)
.. program:: flavor delete
.. code:: bash
os flavor delete
- <flavor>
+ <flavor> [<flavor> ...]
.. _flavor_delete-flavor:
.. describe:: <flavor>
- Flavor to delete (name or ID)
+ Flavor(s) to delete (name or ID)
flavor list
-----------
diff --git a/doc/source/command-objects/floating-ip-pool.rst b/doc/source/command-objects/floating-ip-pool.rst
new file mode 100644
index 0000000..6f074d2
--- /dev/null
+++ b/doc/source/command-objects/floating-ip-pool.rst
@@ -0,0 +1,15 @@
+================
+floating ip pool
+================
+
+Compute v2, Network v2
+
+floating ip pool list
+---------------------
+
+List pools of floating IP addresses
+
+.. program:: floating ip pool list
+.. code:: bash
+
+ os floating ip pool list
diff --git a/doc/source/command-objects/floating-ip.rst b/doc/source/command-objects/floating-ip.rst
new file mode 100644
index 0000000..093bb8e
--- /dev/null
+++ b/doc/source/command-objects/floating-ip.rst
@@ -0,0 +1,82 @@
+===========
+floating ip
+===========
+
+Compute v2, Network v2
+
+floating ip create
+------------------
+
+Create floating IP
+
+.. program:: floating ip create
+.. code:: bash
+
+ os floating ip create
+ [--subnet <subnet>]
+ [--port <port>]
+ [--floating-ip-address <floating-ip-address>]
+ [--fixed-ip-address <fixed-ip-address>]
+ <network>
+
+.. option:: --subnet <subnet>
+
+ Subnet on which you want to create the floating IP (name or ID)
+ *Network version 2 only*
+
+.. option:: --port <port>
+
+ Port to be associated with the floating IP (name or ID)
+ *Network version 2 only*
+
+.. option:: --floating-ip-address <floating-ip-address>
+
+ Floating IP address
+ *Network version 2 only*
+
+.. option:: --fixed-ip-address <fixed-ip-address>
+
+ Fixed IP address mapped to the floating IP
+ *Network version 2 only*
+
+.. describe:: <network>
+
+ Network to allocate floating IP from (name or ID)
+
+floating ip delete
+------------------
+
+Delete floating IP(s)
+
+.. program:: floating ip delete
+.. code:: bash
+
+ os floating ip delete <floating-ip> [<floating-ip> ...]
+
+.. describe:: <floating-ip>
+
+ Floating IP(s) to delete (IP address or ID)
+
+floating ip list
+----------------
+
+List floating IP(s)
+
+.. program:: floating ip list
+.. code:: bash
+
+ os floating ip list
+
+floating ip show
+----------------
+
+Display floating IP details
+
+.. program:: floating ip show
+.. code:: bash
+
+ os floating ip show <floating-ip>
+
+.. describe:: <floating-ip>
+
+ Floating IP to display (IP address or ID)
diff --git a/doc/source/command-objects/host.rst b/doc/source/command-objects/host.rst
index ede62c5..409b834 100644
--- a/doc/source/command-objects/host.rst
+++ b/doc/source/command-objects/host.rst
@@ -9,7 +9,7 @@ The physical computer running a hypervisor.
host list
---------
-List all hosts
+List hosts
.. program:: host list
.. code:: bash
@@ -24,7 +24,7 @@ List all hosts
host set
--------
-Set host command
+Set host properties
.. program:: host set
.. code:: bash
@@ -54,7 +54,7 @@ Set host command
.. describe:: <host>
- The host (name or ID)
+ Host to modify (name only)
host show
---------
diff --git a/doc/source/command-objects/identity-provider.rst b/doc/source/command-objects/identity-provider.rst
index ca773d8..f772511 100644
--- a/doc/source/command-objects/identity-provider.rst
+++ b/doc/source/command-objects/identity-provider.rst
@@ -49,17 +49,17 @@ Create new identity provider
identity provider delete
------------------------
-Delete identity provider
+Delete identity provider(s)
.. program:: identity provider delete
.. code:: bash
os identity provider delete
- <identity-provider>
+ <identity-provider> [<identity-provider> ...]
.. describe:: <identity-provider>
- Identity provider to delete
+ Identity provider(s) to delete
identity provider list
----------------------
diff --git a/doc/source/command-objects/ip-availability.rst b/doc/source/command-objects/ip-availability.rst
index 55b7842..f200ab1 100644
--- a/doc/source/command-objects/ip-availability.rst
+++ b/doc/source/command-objects/ip-availability.rst
@@ -25,7 +25,7 @@ number of allocated IP addresses from that pool.
.. option:: --ip-version {4,6}
List IP availability of given IP version networks
- (Default is 4)
+ (default is 4)
.. option:: --project <project>
diff --git a/doc/source/command-objects/ip-fixed.rst b/doc/source/command-objects/ip-fixed.rst
index 3a55b99..c2447b2 100644
--- a/doc/source/command-objects/ip-fixed.rst
+++ b/doc/source/command-objects/ip-fixed.rst
@@ -8,6 +8,7 @@ ip fixed add
------------
Add fixed IP address to server
+(Deprecated, please use ``server add fixed ip`` instead)
.. program:: ip fixed add
.. code:: bash
@@ -28,6 +29,7 @@ ip fixed remove
---------------
Remove fixed IP address from server
+(Deprecated, please use ``server remove fixed ip`` instead)
.. program:: ip fixed remove
.. code:: bash
diff --git a/doc/source/command-objects/ip-floating-pool.rst b/doc/source/command-objects/ip-floating-pool.rst
index 63a450e..310974c 100644
--- a/doc/source/command-objects/ip-floating-pool.rst
+++ b/doc/source/command-objects/ip-floating-pool.rst
@@ -8,6 +8,7 @@ ip floating pool list
---------------------
List pools of floating IP addresses
+(Deprecated, please use ``floating ip pool list`` instead)
.. program:: ip floating pool list
.. code:: bash
diff --git a/doc/source/command-objects/ip-floating.rst b/doc/source/command-objects/ip-floating.rst
index 869b7af..7b38a38 100644
--- a/doc/source/command-objects/ip-floating.rst
+++ b/doc/source/command-objects/ip-floating.rst
@@ -8,6 +8,7 @@ ip floating add
---------------
Add floating IP address to server
+(Deprecated, please use ``server add floating ip`` instead)
.. program:: ip floating add
.. code:: bash
@@ -28,6 +29,7 @@ ip floating create
------------------
Create new floating IP address
+(Deprecated, please use ``floating ip create`` instead)
.. program:: ip floating create
.. code:: bash
@@ -66,21 +68,24 @@ Create new floating IP address
ip floating delete
------------------
-Delete floating IP
+Delete floating IP(s)
+(Deprecated, please use ``floating ip delete`` instead)
.. program:: ip floating delete
.. code:: bash
- os ip floating delete <floating-ip>
+ os ip floating delete
+ <floating-ip> [<floating-ip> ...]
.. describe:: <floating-ip>
- Floating IP to delete (IP address or ID)
+ Floating IP(s) to delete (IP address or ID)
ip floating list
----------------
List floating IP addresses
+(Deprecated, please use ``floating ip list`` instead)
.. program:: ip floating list
.. code:: bash
@@ -91,6 +96,7 @@ ip floating remove
------------------
Remove floating IP address from server
+(Deprecated, please use ``server remove floating ip`` instead)
.. program:: ip floating remove
.. code:: bash
@@ -111,6 +117,7 @@ ip floating show
----------------
Display floating IP details
+(Deprecated, please use ``floating ip show`` instead)
.. program:: ip floating show
.. code:: bash
diff --git a/doc/source/command-objects/keypair.rst b/doc/source/command-objects/keypair.rst
index 6638b8c..af50a65 100644
--- a/doc/source/command-objects/keypair.rst
+++ b/doc/source/command-objects/keypair.rst
@@ -30,17 +30,17 @@ Create new public key
keypair delete
--------------
-Delete public key
+Delete public key(s)
.. program:: keypair delete
.. code:: bash
os keypair delete
- <key>
+ <key> [<key> ...]
.. describe:: <key>
- Public key to delete (name only)
+ Public key(s) to delete (name only)
keypair list
------------
@@ -70,4 +70,4 @@ Display public key details
.. describe:: <key>
- Public key to display
+ Public key to display (name only)
diff --git a/doc/source/command-objects/mapping.rst b/doc/source/command-objects/mapping.rst
index 25af474..7f61366 100644
--- a/doc/source/command-objects/mapping.rst
+++ b/doc/source/command-objects/mapping.rst
@@ -30,18 +30,18 @@ Create new mapping
mapping delete
--------------
-Delete mapping
+Delete mapping(s)
.. program:: mapping delete
.. code:: bash
os mapping delete
- <mapping>
+ <mapping> [<mapping> ...]
.. _mapping_delete-mapping:
.. describe:: <mapping>
- Mapping to delete
+ Mapping(s) to delete
mapping list
------------
diff --git a/doc/source/command-objects/network-agent.rst b/doc/source/command-objects/network-agent.rst
new file mode 100644
index 0000000..15195a3
--- /dev/null
+++ b/doc/source/command-objects/network-agent.rst
@@ -0,0 +1,83 @@
+=============
+network agent
+=============
+
+A **network agent** is an agent that handles various tasks used to
+implement virtual networks. These agents include neutron-dhcp-agent,
+neutron-l3-agent, neutron-metering-agent, and neutron-lbaas-agent,
+among others. The agent is available when the alive status of the
+agent is "True".
+
+Network v2
+
+network agent delete
+--------------------
+
+Delete network agent(s)
+
+.. program:: network agent delete
+.. code:: bash
+
+ os network agent delete
+ <network-agent> [<network-agent> ...]
+
+.. _network_agent_delete-network-agent:
+.. describe:: <network-agent>
+
+ Network agent(s) to delete (ID only)
+
+network agent list
+------------------
+
+List network agents
+
+.. program:: network agent list
+.. code:: bash
+
+ os network agent list
+
+network agent set
+-----------------
+
+Set network agent properties
+
+.. program:: network agent set
+.. code:: bash
+
+ os network agent set
+ [--description <description>]
+ [--enable | --disable]
+ <network-agent>
+
+.. option:: --description <discription>
+
+ Set network agent description
+
+.. option:: --enable
+
+ Enable network agent
+
+.. option:: --disable
+
+ Disable network agent
+
+.. _network_agent_set-network-agent:
+.. describe:: <network-agent>
+
+ Network agent to modify (ID only)
+
+network agent show
+------------------
+
+Display network agent details
+
+.. program:: network agent show
+.. code:: bash
+
+ os network agent show
+ <network-agent>
+
+.. _network_agent_show-network-agent:
+.. describe:: <network-agent>
+
+ Network agent to display (ID only)
diff --git a/doc/source/command-objects/network-rbac.rst b/doc/source/command-objects/network-rbac.rst
new file mode 100644
index 0000000..8acf097
--- /dev/null
+++ b/doc/source/command-objects/network-rbac.rst
@@ -0,0 +1,123 @@
+============
+network rbac
+============
+
+A **network rbac** is a Role-Based Access Control (RBAC) policy for
+network resources. It enables both operators and users to grant access
+to network resources for specific projects.
+
+Network v2
+
+network rbac create
+-------------------
+
+Create network RBAC policy
+
+.. program:: network rbac create
+.. code:: bash
+
+ os network rbac create
+ --type <type>
+ --action <action>
+ --target-project <target-project> [--target-project-domain <target-project-domain>]
+ [--project <project> [--project-domain <project-domain>]]
+ <rbac-policy>
+
+.. option:: --type <type>
+
+ Type of the object that RBAC policy affects ("qos_policy" or "network") (required)
+
+.. option:: --action <action>
+
+ Action for the RBAC policy ("access_as_external" or "access_as_shared") (required)
+
+.. option:: --target-project <target-project>
+
+ The project to which the RBAC policy will be enforced (name or ID) (required)
+
+.. option:: --target-project-domain <target-project-domain>
+
+ Domain the target project belongs to (name or ID).
+ This can be used in case collisions between project names exist.
+
+.. option:: --project <project>
+
+ The owner project (name or ID)
+
+.. option:: --project-domain <project-domain>
+
+ Domain the project belongs to (name or ID).
+ This can be used in case collisions between project names exist.
+
+.. _network_rbac_create-rbac-policy:
+.. describe:: <rbac-object>
+
+ The object to which this RBAC policy affects (name or ID for network objects, ID only for QoS policy objects)
+
+network rbac delete
+-------------------
+
+Delete network RBAC policy(s)
+
+.. program:: network rbac delete
+.. code:: bash
+
+ os network rbac delete
+ <rbac-policy> [<rbac-policy> ...]
+
+.. _network_rbac_delete-rbac-policy:
+.. describe:: <rbac-policy>
+
+ RBAC policy(s) to delete (ID only)
+
+network rbac list
+-----------------
+
+List network RBAC policies
+
+.. program:: network rbac list
+.. code:: bash
+
+ os network rbac list
+
+network rbac set
+----------------
+
+Set network RBAC policy properties
+
+.. program:: network rbac set
+.. code:: bash
+
+ os network rbac set
+ [--target-project <target-project> [--target-project-domain <target-project-domain>]]
+ <rbac-policy>
+
+.. option:: --target-project <target-project>
+
+ The project to which the RBAC policy will be enforced (name or ID)
+
+.. option:: --target-project-domain <target-project-domain>
+
+ Domain the target project belongs to (name or ID).
+ This can be used in case collisions between project names exist.
+
+.. _network_rbac_set-rbac-policy:
+.. describe:: <rbac-policy>
+
+ RBAC policy to be modified (ID only)
+
+network rbac show
+-----------------
+
+Display network RBAC policy details
+
+.. program:: network rbac show
+.. code:: bash
+
+ os network rbac show
+ <rbac-policy>
+
+.. _network_rbac_show-rbac-policy:
+.. describe:: <rbac-policy>
+
+ RBAC policy (ID only)
diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst
index 8668dae..5d9a5ca 100644
--- a/doc/source/command-objects/network.rst
+++ b/doc/source/command-objects/network.rst
@@ -24,6 +24,7 @@ Create new network
[--enable | --disable]
[--share | --no-share]
[--availability-zone-hint <availability-zone>]
+ [--enable-port-security | --disable-port-security]
[--external [--default | --no-default] | --internal]
[--provider-network-type <provider-network-type>]
[--provider-physical-network <provider-physical-network>]
@@ -72,6 +73,20 @@ Create new network
*Network version 2 only*
+.. option:: --enable-port-security
+
+ Enable port security by default for ports created on
+ this network (default)
+
+ *Network version 2 only*
+
+.. option:: --disable-port-security
+
+ Disable port security by default for ports created on
+ this network
+
+ *Network version 2 only*
+
.. option:: --subnet <subnet>
IPv4 subnet for fixed IPs (in CIDR notation)
@@ -108,7 +123,7 @@ Create new network
.. option:: --provider-network-type <provider-network-type>
The physical mechanism by which the virtual network is implemented.
- The supported options are: flat, gre, local, vlan, vxlan
+ The supported options are: flat, geneve, gre, local, vlan, vxlan.
*Network version 2 only*
@@ -120,7 +135,7 @@ Create new network
.. option:: --provider-segment <provider-segment>
- VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN networks
+ VLAN ID for VLAN networks or Tunnel ID for GENEVE/GRE/VXLAN networks
*Network version 2 only*
@@ -191,6 +206,7 @@ Set network properties
[--name <name>]
[--enable | --disable]
[--share | --no-share]
+ [--enable-port-security | --disable-port-security]
[--external [--default | --no-default] | --internal]
[--provider-network-type <provider-network-type>]
[--provider-physical-network <provider-physical-network>]
@@ -218,6 +234,16 @@ Set network properties
Do not share the network between projects
+.. option:: --enable-port-security
+
+ Enable port security by default for ports created on
+ this network
+
+.. option:: --disable-port-security
+
+ Disable port security by default for ports created on
+ this network
+
.. option:: --external
Set this network as an external network.
@@ -238,7 +264,7 @@ Set network properties
.. option:: --provider-network-type <provider-network-type>
The physical mechanism by which the virtual network is implemented.
- The supported options are: flat, gre, local, vlan, vxlan
+ The supported options are: flat, gre, local, vlan, vxlan.
.. option:: --provider-physical-network <provider-physical-network>
diff --git a/doc/source/command-objects/policy.rst b/doc/source/command-objects/policy.rst
index 195a89f..757b1c5 100644
--- a/doc/source/command-objects/policy.rst
+++ b/doc/source/command-objects/policy.rst
@@ -27,17 +27,17 @@ Create new policy
policy delete
-------------
-Delete policy
+Delete policy(s)
.. program:: policy delete
.. code:: bash
os policy delete
- <policy>
+ <policy> [<policy> ...]
.. describe:: <policy>
- Policy to delete
+ Policy(s) to delete
policy list
-----------
diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst
index e4cf2cd..4d7b95b 100644
--- a/doc/source/command-objects/port.rst
+++ b/doc/source/command-objects/port.rst
@@ -45,7 +45,8 @@ Create new port
.. option:: --device-owner <device-owner>
- Device owner of this port
+ Device owner of this port. This is the entity that uses
+ the port (for example, network:dhcp).
.. option:: --vnic-type <vnic-type>
@@ -54,7 +55,8 @@ Create new port
.. option:: --binding-profile <binding-profile>
- Custom data to be passed as binding:profile: <key>=<value>
+ Custom data to be passed as binding:profile. Data may
+ be passed as <key>=<value> or JSON.
(repeat option to set multiple binding:profile data)
.. option:: --host <host-id>
@@ -112,8 +114,14 @@ List ports
.. code:: bash
os port list
+ [--device-owner <device-owner>]
[--router <router>]
+.. option:: --device-owner <device-owner>
+
+ List only ports with the specified device owner. This is
+ the entity that uses the port (for example, network:dhcp).
+
.. option:: --router <router>
List only ports attached to this router (name or ID)
@@ -153,7 +161,8 @@ Set port properties
.. option:: --device-owner <device-owner>
- Device owner of this port
+ Device owner of this port. This is the entity that uses
+ the port (for example, network:dhcp).
.. option:: --vnic-type <vnic-type>
@@ -162,7 +171,8 @@ Set port properties
.. option:: --binding-profile <binding-profile>
- Custom data to be passed as binding:profile: <key>=<value>
+ Custom data to be passed as binding:profile. Data may
+ be passed as <key>=<value> or JSON.
(repeat option to set multiple binding:profile data)
.. option:: --no-binding-profile
@@ -205,3 +215,32 @@ Display port details
.. describe:: <port>
Port to display (name or ID)
+
+port unset
+----------
+
+Unset port properties
+
+.. program:: port unset
+.. code:: bash
+
+ os port unset
+ [--fixed-ip subnet=<subnet>,ip-address=<ip-address> [...]]
+ [--binding-profile <binding-profile-key> [...]]
+ <port>
+
+.. option:: --fixed-ip subnet=<subnet>,ip-address=<ip-address>
+
+ Desired IP and/or subnet (name or ID) which should be removed
+ from this port: subnet=<subnet>,ip-address=<ip-address>
+ (repeat option to unset multiple fixed IP addresses)
+
+.. option:: --binding-profile <binding-profile-key>
+
+ Desired key which should be removed from binding-profile
+ (repeat option to unset multiple binding:profile data)
+
+.. _port_unset-port:
+.. describe:: <port>
+
+ Port to modify (name or ID)
diff --git a/doc/source/command-objects/quota.rst b/doc/source/command-objects/quota.rst
index dc5e362..381601d 100644
--- a/doc/source/command-objects/quota.rst
+++ b/doc/source/command-objects/quota.rst
@@ -25,6 +25,8 @@ Set quotas for project
[--key-pairs <num-key-pairs>]
[--properties <num-properties>]
[--ram <ram-mb>]
+ [--server-groups <num-server-groups>]
+ [--server-group-members <num-server-group-members>]
# Block Storage settings
[--gigabytes <new-gigabytes>]
@@ -64,6 +66,8 @@ Set quotas for class
[--key-pairs <num-key-pairs>]
[--properties <num-properties>]
[--ram <ram-mb>]
+ [--server-groups <num-server-groups>]
+ [--server-group-members <num-server-group-members>]
# Block Storage settings
[--gigabytes <new-gigabytes>]
@@ -108,6 +112,14 @@ Set quotas for class
New value for the injected-file-size quota
+.. option:: --server-groups <new-server-groups>
+
+ New value for the server-groups quota
+
+.. option:: --server-group-members <new-server-group-members>
+
+ New value for the server-group-members quota
+
.. option:: --floating-ips <new-floating-ips>
New value for the floating-ips quota
diff --git a/doc/source/command-objects/region.rst b/doc/source/command-objects/region.rst
index 1892fc2..b74821a 100644
--- a/doc/source/command-objects/region.rst
+++ b/doc/source/command-objects/region.rst
@@ -33,18 +33,18 @@ Create new region
region delete
-------------
-Delete region
+Delete region(s)
.. program:: region delete
.. code:: bash
os region delete
- <region-id>
+ <region-id> [<region-id> ...]
.. _region_delete-region-id:
.. describe:: <region-id>
- Region ID to delete
+ Region ID(s) to delete
region list
-----------
diff --git a/doc/source/command-objects/role-assignment.rst b/doc/source/command-objects/role-assignment.rst
index 893ebdc..dc970ed 100644
--- a/doc/source/command-objects/role-assignment.rst
+++ b/doc/source/command-objects/role-assignment.rst
@@ -2,7 +2,7 @@
role assignment
===============
-Identity v3
+Identity v2, v3
role assignment list
--------------------
@@ -14,6 +14,7 @@ List role assignments
os role assignment list
[--role <role>]
+ [--role-domain <role-domain>]
[--user <user>]
[--user-domain <user-domain>]
[--group <group>]
@@ -23,11 +24,21 @@ List role assignments
[--project-domain <project-domain>]
[--effective]
[--inherited]
+ [--names]
.. option:: --role <role>
Role to filter (name or ID)
+ .. versionadded:: 3
+
+.. option:: --role-domain <role-domain>
+
+ Domain the role belongs to (name or ID).
+ This can be used in case collisions between role names exist.
+
+ .. versionadded:: 3
+
.. option:: --user <user>
User to filter (name or ID)
@@ -37,19 +48,27 @@ List role assignments
Domain the user belongs to (name or ID).
This can be used in case collisions between user names exist.
+ .. versionadded:: 3
+
.. option:: --group <group>
Group to filter (name or ID)
+ .. versionadded:: 3
+
.. option:: --group-domain <group-domain>
Domain the group belongs to (name or ID).
This can be used in case collisions between group names exist.
+ .. versionadded:: 3
+
.. option:: --domain <domain>
Domain to filter (name or ID)
+ .. versionadded:: 3
+
.. option:: --project <project>
Project to filter (name or ID)
@@ -59,14 +78,29 @@ List role assignments
Domain the project belongs to (name or ID).
This can be used in case collisions between project names exist.
+ .. versionadded:: 3
+
.. option:: --effective
Returns only effective role assignments (defaults to False)
+ .. versionadded:: 3
+
.. option:: --inherited
Specifies if the role grant is inheritable to the sub projects
+ .. versionadded:: 3
+
.. option:: --names
Returns role assignments with names instead of IDs
+
+.. option:: --auth-user
+
+ Returns role assignments for the authenticated user.
+
+.. option:: --auth-project
+
+ Returns role assignments for the project to which the authenticated user
+ is scoped.
diff --git a/doc/source/command-objects/role.rst b/doc/source/command-objects/role.rst
index 48751ed..2ff1f13 100644
--- a/doc/source/command-objects/role.rst
+++ b/doc/source/command-objects/role.rst
@@ -7,7 +7,7 @@ Identity v2, v3
role add
--------
-Add role to a user or group in a project or domain
+Add role assignment to a user or group in a project or domain
.. program:: role add
.. code:: bash
@@ -15,6 +15,7 @@ Add role to a user or group in a project or domain
os role add
--domain <domain> | --project <project> [--project-domain <project-domain>]
--user <user> [--user-domain <user-domain>] | --group <group> [--group-domain <group-domain>]
+ --role-domain <role-domain>
--inherited
<role>
@@ -65,6 +66,13 @@ Add role to a user or group in a project or domain
.. versionadded:: 3
+.. option:: --role-domain <role-domain>
+
+ Domain the role belongs to (name or ID).
+ This must be specified when the name of a domain specific role is used.
+
+ .. versionadded:: 3
+
.. describe:: <role>
Role to add to <project>:<user> (name or ID)
@@ -79,8 +87,15 @@ Create new role
os role create
[--or-show]
+ [--domain <domain>]
<name>
+.. option:: --domain <domain>
+
+ Domain the role belongs to (name or ID).
+
+ .. versionadded:: 3
+
.. option:: --or-show
Return existing role
@@ -101,11 +116,18 @@ Delete role(s)
os role delete
<role> [<role> ...]
+ [--domain <domain>]
.. describe:: <role>
Role to delete (name or ID)
+.. option:: --domain <domain>
+
+ Domain the role belongs to (name or ID).
+
+ .. versionadded:: 3
+
role list
---------
@@ -123,31 +145,34 @@ List roles
Filter roles by <domain> (name or ID)
- .. versionadded:: 3
+ (Deprecated if being used to list assignments in conjunction with the
+ ``--user <user>``, option, please use ``role assignment list`` instead)
.. option:: --project <project>
Filter roles by <project> (name or ID)
- .. versionadded:: 3
+ (Deprecated, please use ``role assignment list`` instead)
.. option:: --user <user>
Filter roles by <user> (name or ID)
- .. versionadded:: 3
+ (Deprecated, please use ``role assignment list`` instead)
.. option:: --group <group>
Filter roles by <group> (name or ID)
- .. versionadded:: 3
+ (Deprecated, please use ``role assignment list`` instead)
.. option:: --user-domain <user-domain>
Domain the user belongs to (name or ID).
This can be used in case collisions between user names exist.
+ (Deprecated, please use ``role assignment list`` instead)
+
.. versionadded:: 3
.. option:: --group-domain <group-domain>
@@ -155,6 +180,8 @@ List roles
Domain the group belongs to (name or ID).
This can be used in case collisions between group names exist.
+ (Deprecated, please use ``role assignment list`` instead)
+
.. versionadded:: 3
.. option:: --project-domain <project-domain>
@@ -162,18 +189,22 @@ List roles
Domain the project belongs to (name or ID).
This can be used in case collisions between project names exist.
+ (Deprecated, please use ``role assignment list`` instead)
+
.. versionadded:: 3
.. option:: --inherited
Specifies if the role grant is inheritable to the sub projects.
+ (Deprecated, please use ``role assignment list`` instead)
+
.. versionadded:: 3
role remove
-----------
-Remove role from domain/project : user/group
+Remove role assignment from domain/project : user/group
.. program:: role remove
.. code:: bash
@@ -181,6 +212,7 @@ Remove role from domain/project : user/group
os role remove
--domain <domain> | --project <project> [--project-domain <project-domain>]
--user <user> [--user-domain <user-domain>] | --group <group> [--group-domain <group-domain>]
+ --role-domain <role-domain>
--inherited
<role>
@@ -231,6 +263,13 @@ Remove role from domain/project : user/group
.. versionadded:: 3
+.. option:: --role-domain <role-domain>
+
+ Domain the role belongs to (name or ID).
+ This must be specified when the name of a domain specific role is used.
+
+ .. versionadded:: 3
+
.. describe:: <role>
Role to remove (name or ID)
@@ -247,12 +286,19 @@ Set role properties
os role set
[--name <name>]
+ [--domain <domain>]
<role>
.. option:: --name <name>
Set role name
+.. option:: --domain <domain>
+
+ Domain the role belongs to (name or ID).
+
+ .. versionadded:: 3
+
.. describe:: <role>
Role to modify (name or ID)
@@ -266,8 +312,15 @@ Display role details
.. code:: bash
os role show
+ [--domain <domain>]
<role>
+.. option:: --domain <domain>
+
+ Domain the role belongs to (name or ID).
+
+ .. versionadded:: 3
+
.. describe:: <role>
Role to display (name or ID)
diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst
index 9ca7661..bbdca05 100644
--- a/doc/source/command-objects/router.rst
+++ b/doc/source/command-objects/router.rst
@@ -150,7 +150,7 @@ Remove a port from a router
.. describe:: <port>
- Port to be removed (name or ID)
+ Port to be removed and deleted (name or ID)
router remove subnet
--------------------
@@ -240,3 +240,27 @@ Display router details
.. describe:: <router>
Router to display (name or ID)
+
+router unset
+------------
+
+Unset router properties
+
+.. program:: router unset
+.. code:: bash
+
+ os router unset
+ [--route destination=<subnet>,gateway=<ip-address>]
+ <router>
+
+.. option:: --route destination=<subnet>,gateway=<ip-address>
+
+ Routes to be removed from the router
+ destination: destination subnet (in CIDR notation)
+ gateway: nexthop IP address
+ (repeat option to unset multiple routes)
+
+.. _router_unset-router:
+.. describe:: <router>
+
+ Router to modify (name or ID)
diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst
index 97cce35..5284b2d 100644
--- a/doc/source/command-objects/security-group-rule.rst
+++ b/doc/source/command-objects/security-group-rule.rst
@@ -104,17 +104,17 @@ Create a new security group rule
security group rule delete
--------------------------
-Delete a security group rule
+Delete security group rule(s)
.. program:: security group rule delete
.. code:: bash
os security group rule delete
- <rule>
+ <rule> [<rule> ...]
.. describe:: <rule>
- Security group rule to delete (ID only)
+ Security group rule(s) to delete (ID only)
security group rule list
------------------------
diff --git a/doc/source/command-objects/security-group.rst b/doc/source/command-objects/security-group.rst
index 3af11b5..ba05455 100644
--- a/doc/source/command-objects/security-group.rst
+++ b/doc/source/command-objects/security-group.rst
@@ -45,17 +45,17 @@ Create a new security group
security group delete
---------------------
-Delete a security group
+Delete security group(s)
.. program:: security group delete
.. code:: bash
os security group delete
- <group>
+ <group> [<group> ...]
.. describe:: <group>
- Security group to delete (name or ID)
+ Security group(s) to delete (name or ID)
security group list
-------------------
diff --git a/doc/source/command-objects/server-image.rst b/doc/source/command-objects/server-image.rst
index 8b48934..eb44e47 100644
--- a/doc/source/command-objects/server-image.rst
+++ b/doc/source/command-objects/server-image.rst
@@ -10,7 +10,7 @@ Compute v2
server image create
-------------------
-Create a new disk image from a running server
+Create a new server disk image from an existing server
.. program:: server image create
.. code:: bash
@@ -22,12 +22,12 @@ Create a new disk image from a running server
.. option:: --name <image-name>
- Name of new image (default is server name)
+ Name of new disk image (default: server name)
.. option:: --wait
- Wait for image create to complete
+ Wait for operation to complete
.. describe:: <server>
- Server (name or ID)
+ Server to create image (name or ID)
diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst
index f11355b..f099c27 100644
--- a/doc/source/command-objects/server.rst
+++ b/doc/source/command-objects/server.rst
@@ -4,6 +4,46 @@ server
Compute v2
+server add fixed ip
+-------------------
+
+Add fixed IP address to server
+
+.. program:: server add fixed ip
+.. code:: bash
+
+ os server add fixed ip
+ <server>
+ <network>
+
+.. describe:: <server>
+
+ Server (name or ID) to receive the fixed IP address
+
+.. describe:: <network>
+
+ Network (name or ID) to allocate the fixed IP address from
+
+server add floating ip
+----------------------
+
+Add floating IP address to server
+
+.. program:: server add floating ip
+.. code:: bash
+
+ os server add floating ip
+ <server>
+ <ip-address>
+
+.. describe:: <server>
+
+ Server (name or ID) to receive the floating IP address
+
+.. describe:: <ip-address>
+
+ Floating IP address (IP address only) to assign to server
+
server add security group
-------------------------
@@ -418,6 +458,46 @@ Rebuild server
Server (name or ID)
+server remove fixed ip
+----------------------
+
+Remove fixed IP address from server
+
+.. program:: server remove fixed ip
+.. code:: bash
+
+ os server remove fixed ip
+ <server>
+ <ip-address>
+
+.. describe:: <server>
+
+ Server (name or ID) to remove the fixed IP address from
+
+.. describe:: <ip-address>
+
+ Fixed IP address (IP address only) to remove from the server
+
+server remove floating ip
+-------------------------
+
+Remove floating IP address from server
+
+.. program:: server remove floating ip
+.. code:: bash
+
+ os server remove floating ip
+ <server>
+ <ip-address>
+
+.. describe:: <server>
+
+ Server (name or ID) to remove the floating IP address from
+
+.. describe:: <ip-address>
+
+ Floating IP address (IP address only) to remove from server
+
server remove security group
----------------------------
@@ -463,7 +543,7 @@ server rescue
Put server in rescue mode
-.. program:: server rescure
+.. program:: server rescue
.. code:: bash
os server rescue
diff --git a/doc/source/command-objects/service-provider.rst b/doc/source/command-objects/service-provider.rst
index 963493b..e0fbba2 100644
--- a/doc/source/command-objects/service-provider.rst
+++ b/doc/source/command-objects/service-provider.rst
@@ -48,17 +48,17 @@ Create new service provider
service provider delete
-----------------------
-Delete service provider
+Delete service provider(s)
.. program:: service provider delete
.. code:: bash
os service provider delete
- <service-provider>
+ <service-provider> [<service-provider> ...]
.. describe:: <service-provider>
- Service provider to delete
+ Service provider(s) to delete
service provider list
---------------------
diff --git a/doc/source/command-objects/service.rst b/doc/source/command-objects/service.rst
index 352f68a..b5ec2d9 100644
--- a/doc/source/command-objects/service.rst
+++ b/doc/source/command-objects/service.rst
@@ -46,18 +46,18 @@ Create new service
service delete
--------------
-Delete service
+Delete service(s)
.. program:: service delete
.. code-block:: bash
os service delete
- <service>
+ <service> [<service> ...]
.. _service_delete-type:
.. describe:: <service>
- Service to delete (type, name or ID)
+ Service(s) to delete (type, name or ID)
service list
------------
@@ -117,7 +117,7 @@ Set service properties
.. _service_set-service:
.. describe:: <service>
- Service to update (type, name or ID)
+ Service to modify (type, name or ID)
service show
------------
diff --git a/doc/source/command-objects/snapshot.rst b/doc/source/command-objects/snapshot.rst
index 9033011..ce2d9c8 100644
--- a/doc/source/command-objects/snapshot.rst
+++ b/doc/source/command-objects/snapshot.rst
@@ -16,6 +16,7 @@ Create new snapshot
[--name <name>]
[--description <description>]
[--force]
+ [--property <key=value> [...] ]
<volume>
.. option:: --name <name>
@@ -30,6 +31,12 @@ Create new snapshot
Create a snapshot attached to an instance. Default is False
+.. option:: --property <key=value>
+
+ Set a property to this snapshot (repeat option to set multiple properties)
+
+ *Volume version 2 only*
+
.. _snapshot_create-snapshot:
.. describe:: <volume>
@@ -61,6 +68,9 @@ List snapshots
os snapshot list
[--all-projects]
+ [--long]
+ [--limit <limit>]
+ [--marker <marker>]
.. option:: --all-projects
@@ -70,6 +80,18 @@ List snapshots
List additional fields in output
+.. option:: --limit <limit>
+
+ Maximum number of snapshots to display
+
+ *Volume version 2 only*
+
+.. option:: --marker <marker>
+
+ The last snapshot ID of the previous page
+
+ *Volume version 2 only*
+
snapshot set
------------
diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst
index 8abf25a..005b835 100644
--- a/doc/source/command-objects/subnet-pool.rst
+++ b/doc/source/command-objects/subnet-pool.rst
@@ -81,18 +81,18 @@ Create subnet pool
subnet pool delete
------------------
-Delete subnet pool
+Delete subnet pool(s)
.. program:: subnet pool delete
.. code:: bash
os subnet pool delete
- <subnet-pool>
+ <subnet-pool> [<subnet-pool> ...]
.. _subnet_pool_delete-subnet-pool:
.. describe:: <subnet-pool>
- Subnet pool to delete (name or ID)
+ Subnet pool(s) to delete (name or ID)
subnet pool list
----------------
@@ -185,3 +185,25 @@ Display subnet pool details
.. describe:: <subnet-pool>
Subnet pool to display (name or ID)
+
+subnet pool unset
+-----------------
+
+Unset subnet pool properties
+
+.. program:: subnet pool unset
+.. code:: bash
+
+ os subnet pool unset
+ [--pool-prefix <pool-prefix> [...]]
+ <subnet-pool>
+
+.. option:: --pool-prefix <pool-prefix>
+
+ Remove subnet pool prefixes (in CIDR notation).
+ (repeat option to unset multiple prefixes).
+
+.. _subnet_pool_unset-subnet-pool:
+.. describe:: <subnet-pool>
+
+ Subnet pool to modify (name or ID)
diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst
index ff6354e..e8740e5 100644
--- a/doc/source/command-objects/subnet.rst
+++ b/doc/source/command-objects/subnet.rst
@@ -28,6 +28,7 @@ Create new subnet
[--ip-version {4,6}]
[--ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}]
[--ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}]
+ [--network-segment <network-segment>]
--network <network>
<name>
@@ -82,7 +83,7 @@ Create new subnet
'auto': Gateway address should automatically be chosen from
within the subnet itself, 'none': This subnet will not use
a gateway, e.g.: ``--gateway 192.168.9.1``, ``--gateway auto``,
- ``--gateway none`` (default is 'auto')
+ ``--gateway none`` (default is 'auto').
.. option:: --host-route destination=<subnet>,gateway=<ip-address>
@@ -107,6 +108,14 @@ Create new subnet
IPv6 address mode, valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]
+.. option:: --network-segment <network-segment>
+
+ Network segment to associate with this subnet (ID only)
+
+ .. caution:: This is a beta command option and subject
+ to change. Use global option ``--os-beta-command``
+ to enable this command option.
+
.. option:: --network <network>
Network this subnet belongs to (name or ID)
@@ -119,18 +128,18 @@ Create new subnet
subnet delete
-------------
-Delete a subnet
+Delete subnet(s)
.. program:: subnet delete
.. code:: bash
os subnet delete
- <subnet>
+ <subnet> [<subnet> ...]
.. _subnet_delete-subnet:
.. describe:: <subnet>
- Subnet to delete (name or ID)
+ Subnet(s) to delete (name or ID)
subnet list
-----------
@@ -142,6 +151,8 @@ List subnets
os subnet list
[--long]
+ [--ip-version {4,6}]
+ [--dhcp | --no-dhcp]
.. option:: --long
@@ -149,7 +160,16 @@ List subnets
.. option:: --ip-version {4, 6}
- List only subnets of given IP version in output
+ List only subnets of given IP version in output.
+ Allowed values for IP version are 4 and 6.
+
+.. option:: --dhcp
+
+ List subnets which have DHCP enabled
+
+.. option:: --no-dhcp
+
+ List subnets which have DHCP disabled
subnet set
----------
@@ -191,7 +211,7 @@ Set subnet properties
Specify a gateway for the subnet. The options are:
<ip-address>: Specific IP address to use as the gateway,
'none': This subnet will not use a gateway,
- e.g.: ``--gateway 192.168.9.1``, ``--gateway none``
+ e.g.: ``--gateway 192.168.9.1``, ``--gateway none``.
.. option:: --host-route destination=<subnet>,gateway=<ip-address>
@@ -226,3 +246,41 @@ Display subnet details
.. describe:: <subnet>
Subnet to display (name or ID)
+
+subnet unset
+------------
+
+Unset subnet properties
+
+.. program:: subnet unset
+.. code:: bash
+
+ os subnet unset
+ [--allocation-pool start=<ip-address>,end=<ip-address> [...]]
+ [--dns-nameserver <dns-nameserver> [...]]
+ [--host-route destination=<subnet>,gateway=<ip-address> [...]]
+ <subnet>
+
+.. option:: --dns-nameserver <dns-nameserver>
+
+ DNS server to be removed from this subnet
+ (repeat option to unset multiple DNS servers)
+
+.. option:: --allocation-pool start=<ip-address>,end=<ip-address>
+
+ Allocation pool to be removed from this subnet e.g.:
+ ``start=192.168.199.2,end=192.168.199.254``
+ (repeat option to unset multiple Allocation pools)
+
+.. option:: --host-route destination=<subnet>,gateway=<ip-address>
+
+ Route to be removed from this subnet e.g.:
+ ``destination=10.10.0.0/16,gateway=192.168.71.254``
+ destination: destination subnet (in CIDR notation)
+ gateway: nexthop IP address
+ (repeat option to unset multiple host routes)
+
+.. _subnet_unset-subnet:
+.. describe:: <subnet>
+
+ subnet to modify (name or ID)
diff --git a/doc/source/command-objects/user.rst b/doc/source/command-objects/user.rst
index fbc0b6d..a9fee72 100644
--- a/doc/source/command-objects/user.rst
+++ b/doc/source/command-objects/user.rst
@@ -95,7 +95,7 @@ Delete user(s)
.. _user_delete-user:
.. describe:: <user>
- User to delete (name or ID)
+ User(s) to delete (name or ID)
user list
---------
@@ -143,6 +143,7 @@ Set user properties
[--name <name>]
[--project <project> [--project-domain <project-domain>]]
[--password <password>]
+ [--password-prompt]
[--email <email-address>]
[--description <description>]
[--enable|--disable]
diff --git a/doc/source/command-objects/volume-backup.rst b/doc/source/command-objects/volume-backup.rst
new file mode 100644
index 0000000..3a8f021
--- /dev/null
+++ b/doc/source/command-objects/volume-backup.rst
@@ -0,0 +1,132 @@
+=============
+volume backup
+=============
+
+Block Storage v1, v2
+
+volume backup create
+--------------------
+
+Create new volume backup
+
+.. program:: volume backup create
+.. code:: bash
+
+ os volume backup create
+ [--container <container>]
+ [--name <name>]
+ [--description <description>]
+ [--snapshot <snapshot>]
+ [--force]
+ [--incremental]
+ <volume>
+
+.. option:: --container <container>
+
+ Optional backup container name
+
+.. option:: --name <name>
+
+ Name of the backup
+
+.. option:: --description <description>
+
+ Description of the backup
+
+.. option:: --snapshot <snapshot>
+
+ Snapshot to backup (name or ID)
+
+ *Volume version 2 only*
+
+.. option:: --force
+
+ Allow to back up an in-use volume
+
+ *Volume version 2 only*
+
+.. option:: --incremental
+
+ Perform an incremental backup
+
+ *Volume version 2 only*
+
+.. _volume_backup_create-backup:
+.. describe:: <volume>
+
+ Volume to backup (name or ID)
+
+volume backup delete
+--------------------
+
+Delete volume backup(s)
+
+.. program:: volume backup delete
+.. code:: bash
+
+ os volume backup delete
+ [--force]
+ <backup> [<backup> ...]
+
+.. option:: --force
+
+ Allow delete in state other than error or available
+
+ *Volume version 2 only*
+
+.. _volume_backup_delete-backup:
+.. describe:: <backup>
+
+ Backup(s) to delete (name or ID)
+
+volume backup list
+------------------
+
+List volume backups
+
+.. program:: volume backup list
+.. code:: bash
+
+ os volume backup list
+
+.. _volume_backup_list-backup:
+.. option:: --long
+
+ List additional fields in output
+
+volume backup restore
+---------------------
+
+Restore volume backup
+
+.. program:: volume backup restore
+.. code:: bash
+
+ os volume backup restore
+ <backup>
+ <volume>
+
+.. _volume_backup_restore-backup:
+.. describe:: <backup>
+
+ Backup to restore (name or ID)
+
+.. describe:: <volume>
+
+ Volume to restore to (name or ID)
+
+volume backup show
+------------------
+
+Display volume backup details
+
+.. program:: volume backup show
+.. code:: bash
+
+ os volume backup show
+ <backup>
+
+.. _volume_backup_show-backup:
+.. describe:: <backup>
+
+ Backup to display (name or ID)
diff --git a/doc/source/command-objects/volume-qos.rst b/doc/source/command-objects/volume-qos.rst
index 2d9d14a..54e5096 100644
--- a/doc/source/command-objects/volume-qos.rst
+++ b/doc/source/command-objects/volume-qos.rst
@@ -58,8 +58,13 @@ Delete QoS specification
.. code:: bash
os volume qos delete
+ [--force]
<qos-spec> [<qos-spec> ...]
+.. option:: --force
+
+ Allow to delete in-use QoS specification(s)
+
.. describe:: <qos-spec>
QoS specification(s) to delete (name or ID)
@@ -69,7 +74,7 @@ volume qos disassociate
Disassociate a QoS specification from a volume type
-.. program:: volume qos disassoiate
+.. program:: volume qos disassociate
.. code:: bash
os volume qos disassociate
diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst
index 50acf9f..d93532c 100644
--- a/doc/source/command-objects/volume-type.rst
+++ b/doc/source/command-objects/volume-type.rst
@@ -16,6 +16,8 @@ Create new volume type
[--description <description>]
[--public | --private]
[--property <key=value> [...] ]
+ [--project <project>]
+ [--project-domain <project-domain>]
<name>
.. option:: --description <description>
@@ -40,6 +42,20 @@ Create new volume type
Set a property on this volume type (repeat option to set multiple properties)
+.. option:: --project <project>
+
+ Allow <project> to access private type (name or ID)
+ (Must be used with :option:`--private` option)
+
+ *Volume version 2 only*
+
+.. option:: --project-domain <project-domain>
+
+ Domain the project belongs to (name or ID).
+ This can be used in case collisions between project names exist.
+
+ *Volume version 2 only*
+
.. _volume_type_create-name:
.. describe:: <name>
@@ -48,18 +64,18 @@ Create new volume type
volume type delete
------------------
-Delete volume type
+Delete volume type(s)
.. program:: volume type delete
.. code:: bash
os volume type delete
- <volume-type>
+ <volume-type> [<volume-type> ...]
.. _volume_type_delete-volume-type:
.. describe:: <volume-type>
- Volume type to delete (name or ID)
+ Volume type(s) to delete (name or ID)
volume type list
----------------
@@ -71,11 +87,24 @@ List volume types
os volume type list
[--long]
+ [--public | --private]
.. option:: --long
List additional fields in output
+.. option:: --public
+
+ List only public types
+
+ *Volume version 2 only*
+
+.. option:: --private
+
+ List only private types (admin only)
+
+ *Volume version 2 only*
+
volume type set
---------------
@@ -129,7 +158,6 @@ volume type show
Display volume type details
-
.. program:: volume type show
.. code:: bash
diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst
index a51d111..8efa8a6 100644
--- a/doc/source/command-objects/volume.rst
+++ b/doc/source/command-objects/volume.rst
@@ -88,13 +88,19 @@ Delete volume(s)
.. code:: bash
os volume delete
- [--force]
+ [--force | --purge]
<volume> [<volume> ...]
.. option:: --force
Attempt forced removal of volume(s), regardless of state (defaults to False)
+.. option:: --purge
+
+ Remove any snapshots along with volume(s) (defaults to False)
+
+ *Volume version 2 only*
+
.. _volume_delete-volume:
.. describe:: <volume>
@@ -174,6 +180,7 @@ Set volume properties
[--description <description>]
[--property <key=value> [...] ]
[--image-property <key=value> [...] ]
+ [--state <state>]
<volume>
.. option:: --name <name>
@@ -203,6 +210,14 @@ Set volume properties
*Volume version 2 only*
+.. option:: --state <state>
+
+ New volume state
+ ("available", "error", "creating", "deleting", "in-use",
+ "attaching", "detaching", "error_deleting" or "maintenance")
+
+ *Volume version 2 only*
+
.. _volume_set-volume:
.. describe:: <volume>
diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst
index 5cb8468..a833d1d 100644
--- a/doc/source/command-options.rst
+++ b/doc/source/command-options.rst
@@ -14,6 +14,12 @@ for defining and using options in all situations. The alternative of only
using it when necessary leads to errors when copy-n-paste is used for a
new command without understanding why or why not that instance is correct.
+The :doc:`Human Interface Guide <humaninterfaceguide>`
+describes the guildelines for option names and usage. In short:
+ * All option names shall be GNU-style long names (two leading dashes).
+ * Some global options may have short names, generally limited to those defined
+ in support libraries such as ``cliff``.
+
General Command Options
=======================
@@ -30,7 +36,7 @@ prepended (such as in the traditional GNU option usage) like `--share` and
In order to handle those APIs that behave differently when a field is set to
`None` and when the field is not present in a passed argument list or dict,
each of the boolean options shall set its own variable to `True` as part of
-a mutiually exclusive group, rather than the more common configuration of
+a mutually exclusive group, rather than the more common configuration of
setting a single destination variable `True` or `False` directly. This allows
us to detect the situation when neither option is present (both variables will
be `False`) and act accordingly for those APIs where this matters.
@@ -205,6 +211,36 @@ An example handler in `take_action()` for `unset` action:
if parsed_args.no_example_property:
kwargs['example_property'] = []
+Required Options
+----------------
+
+Some options have no default value and the API does not allow them to be
+`None`, then these options are always required when users use the command
+to which these options belong.
+
+Required options must be validated by the CLI to avoid omissions. The CLI
+validation may provide an error message for the user if a required option
+is not specified.
+(for example: ``error: argument --test is required``)
+
+.. option:: --test
+
+ Test option (required)
+
+Implementation
+~~~~~~~~~~~~~~
+
+The parser declaration should look like this:
+
+.. code-block:: python
+
+ parser.add_argument(
+ '--test',
+ metavar='<test>',
+ required=True,
+ help=_('Test option (required)'),
+ )
+
List Command Options
====================
diff --git a/doc/source/commands.rst b/doc/source/commands.rst
index 9fb0555..023be87 100644
--- a/doc/source/commands.rst
+++ b/doc/source/commands.rst
@@ -8,11 +8,12 @@ Commands take the form::
openstack [<global-options>] <object-1> <action> [<object-2>] [<command-arguments>]
-* All long options names begin with two dashes (``--``) and use a single dash
+.. NOTE::
+
+ All long options names begin with two dashes (``--``) and use a single dash
(``-``) internally between words (``--like-this``). Underscores (``_``) are
not used in option names.
-
Global Options
--------------
@@ -78,7 +79,7 @@ referring to both Compute and Volume quotas.
* ``command``: (**Internal**) installed commands in the OSC process
* ``compute agent``: (**Compute**) a cloud Compute agent available to a hypervisor
* ``compute service``: (**Compute**) a cloud Compute process running on a host
-* ``configuration``: (**Internal**) openstack client configuration
+* ``configuration``: (**Internal**) OpenStack client configuration
* ``console log``: (**Compute**) server console text dump
* ``console url``: (**Compute**) server remote console URL
* ``consumer``: (**Identity**) OAuth-based delegatee
@@ -89,7 +90,10 @@ referring to both Compute and Volume quotas.
* ``endpoint``: (**Identity**) the base URL used to contact a specific service
* ``extension``: (**Compute**, **Identity**, **Network**, **Volume**) OpenStack server API extensions
* ``federation protocol``: (**Identity**) the underlying protocol used while federating identities
-* ``flavor``: (**Compute**) predefined server configurations: ram, root disk, etc
+* ``flavor``: (**Compute**) predefined server configurations: ram, root disk and so on
+* ``fixed ip``: (**Compute**, **Network**) - an internal IP address assigned to a server
+* ``floating ip``: (**Compute**, **Network**) - a public IP address that can be mapped to a server
+* ``floating ip pool``: (**Compute**, **Network**) - a pool of public IP addresses
* ``group``: (**Identity**) a grouping of users
* ``host``: (**Compute**) - the physical computer running compute services
* ``hypervisor``: (**Compute**) the virtual machine manager
@@ -105,6 +109,8 @@ referring to both Compute and Volume quotas.
* ``mapping``: (**Identity**) a definition to translate identity provider attributes to Identity concepts
* ``module``: (**Internal**) - installed Python modules in the OSC process
* ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources
+* ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks
+* ``network rbac``: (**Network**) - an RBAC policy for network resources
* ``network segment``: (**Network**) - a segment of a virtual network
* ``object``: (**Object Storage**) a single file in the Object Storage
* ``object store account``: (**Object Storage**) owns a group of Object Storage resources
@@ -156,7 +162,7 @@ list check out :doc:`plugin-commands`.
* ``cluster event``: (**Clustering (Senlin)**)
* ``cluster members``: (**Clustering (Senlin)**)
* ``cluster node``: (**Clustering (Senlin)**)
-* ``cluster policy``: (**CLustering (Senlin)**)
+* ``cluster policy``: (**Clustering (Senlin)**)
* ``cluster policy binding``: (**Clustering (Senlin)**)
* ``cluster policy type``: (**Clustering (Senlin)**)
* ``cluster profile``: (**Clustering (Senlin)**)
@@ -173,6 +179,8 @@ list check out :doc:`plugin-commands`.
* ``dataprocessing plugin``: (**Data Processing (Sahara)**)
* ``message-broker cluster``: (**Message Broker (Cue)**)
* ``message flavor``: (**Messaging (Zaqar)**)
+* ``network subport``: (**Networking (Neutron)**)
+* ``network trunk``: (**Networking (Neutron)**)
* ``orchestration resource``: (**Orchestration (Heat)**)
* ``orchestration template``: (**Orchestration (Heat)**)
* ``pool``: (**Messaging (Zaqar)**)
@@ -190,7 +198,7 @@ list check out :doc:`plugin-commands`.
* ``stack resource``: (**Orchestration (Heat)**)
* ``stack snapshot``: (**Orchestration (Heat)**)
* ``stack template``: (**Orchestration (Heat)**)
-* ``task exeuction``: (**Workflow Engine (Mistral)**)
+* ``task execution``: (**Workflow Engine (Mistral)**)
* ``tld``: (**DNS (Designate)**)
* ``workbook``: (**Workflow Engine (Mistral)**)
* ``workflow``: (**Workflow Engine (Mistral)**)
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index d80b3b3..efef2b9 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -214,3 +214,59 @@ The logging level for `--log-file` can be set by using following options.
* `-v, --verbose`
* `-q, --quiet`
* `--debug`
+
+Locale and Language Support
+---------------------------
+
+Full support for languages is included as of OpenStackClient 3.0.0. Here are a
+few tips to ensure you have a correct configuration.
+
+Verify preferred python encoding
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Please perform the following to diagnose ensure locale settings are correct.
+Run python interactively and print the preferred encoding value, e.g.:
+
+::
+
+ $ python -c "import locale; print locale.getpreferredencoding()"
+
+If the value is ``ascii`` or ``ANSI_X3.4-1968`` or any other equivalent name for
+ASCII the problem is in your environment. You most likely do not have your LANG
+environment variable set correctly.
+
+Check the LANG environment variable
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``LANG`` should be of the form: `lang_code`_`[region_code]`.`encoding`.
+For example, it may look like: ``en_US.UTF-8``
+
+The critical part here is the `encoding` value of ``UTF-8``. Python will look
+up locale information and if it finds an encoding value, it will set the
+encoding property of stdin, stdout and stderr to the value found in your
+environment, if it's not defined in your environment it defaults to ASCII.
+
+Redirecting output
+~~~~~~~~~~~~~~~~~~
+
+The above only occurs if stdin, stdout and stderr are attached to a TTY. If
+redirecting data the encoding on these streams will default to the default
+encoding which is set in the `site.py` of your Python distribution, which
+defaults to ASCII. A workaround for this is to set ``PYTHONIOENCODING`` to UTF8.
+
+::
+
+ $ PYTHONIOENCODING=utf-8
+
+A final note about DevStack
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A common post devstack operation is to source the ``openrc`` file to set up
+environment variables. Doing so will unset the default ``LANG`` environment
+variable in your terminal, which will cause the preferred python encoding to
+be ``ascii``. We recommend either setting these environment variables
+independently or using the ``devstack`` or ``devstack-admin`` os-cloud profile.
+
+::
+
+ $ openstack project list --os-cloud devstack-admin
diff --git a/doc/source/developing.rst b/doc/source/developing.rst
index 2ddaeb2..c5092cc 100644
--- a/doc/source/developing.rst
+++ b/doc/source/developing.rst
@@ -15,17 +15,40 @@ please refer to the `OpenStack IRC meetings`_ page.
Testing
-------
-Using ``tox``
-=============
+Tox prerequisites and installation
+===================================
-Before running tests, you should have ``tox`` installed and available in your
-environment:
+Install the prerequisites for Tox:
+
+* On Ubuntu or Debian:
+
+ .. code-block:: bash
+
+ $ apt-get install gcc gettext python-dev libxml2-dev libxslt1-dev \
+ zlib1g-dev
+
+ You may need to use pip install for some packages.
+
+
+* On RHEL or CentOS including Fedora:
+
+ .. code-block:: bash
+
+ $ yum install gcc python-devel libxml2-devel libxslt-devel
+
+* On openSUSE or SUSE linux Enterprise:
+
+ .. code-block:: bash
+
+ $ zypper install gcc python-devel libxml2-devel libxslt-devel
+
+Install python-tox:
.. code-block:: bash
$ pip install tox
-To execute the full suite of tests maintained within OpenStackClient, run:
+To run the full suite of tests maintained within OpenStackClient.
.. code-block:: bash
@@ -37,9 +60,9 @@ To execute the full suite of tests maintained within OpenStackClient, run:
virtualenvs. You can later use the ``-r`` option with ``tox`` to rebuild
your virtualenv in a similar manner.
-To run tests for one or more specific test environments (for example, the most
-common configuration of Python 2.7 and PEP-8), list the environments with the
-``-e`` option, separated by spaces:
+
+To run tests for one or more specific test environments(for example, the most common configuration of
+Python 2.7 and PEP-8), list the environments with the ``-e`` option, separated by spaces:
.. code-block:: bash
@@ -53,7 +76,7 @@ Running functional tests
OpenStackClient also maintains a set of functional tests that are optimally
designed to be run against OpenStack's gate. Optionally, a developer may
choose to run these tests against any OpenStack deployment, however depending
-on the services available, results will vary.
+on the services available, results vary.
To run the entire suite of functional tests:
@@ -70,33 +93,34 @@ To run a specific functional test:
Running with PDB
================
-Using PDB breakpoints with ``tox`` and ``testr`` normally doesn't work since
+Using PDB breakpoints with ``tox`` and ``testr`` normally does not work since
the tests fail with a `BdbQuit` exception rather than stopping at the
breakpoint.
To run with PDB breakpoints during testing, use the `debug` ``tox`` environment
-rather than ``py27``. Here's an example, passing the name of a test since
-you'll normally only want to run the test that hits your breakpoint:
+rather than ``py27``. For example, passing a test name since you will normally
+only want to run the test that hits your breakpoint:
.. code-block:: bash
- $ tox -e debug opentackclient.tests.identity.v3.test_group
+ $ tox -e debug openstackclient.tests.identity.v3.test_group
+
+For reference, the `debug`_ ``tox`` environment implements the instructions
-For reference, the `debug` ``tox`` environment implements the instructions
-here: https://wiki.openstack.org/wiki/Testr#Debugging_.28pdb.29_Tests
+.. _`debug`: https://wiki.openstack.org/wiki/Testr#Debugging_.28pdb.29_Tests
Building the Documentation
--------------------------
The documentation is generated with Sphinx using the ``tox`` command. To
-create HTML docs, run the following:
+create HTML docs, run the commands:
.. code-block:: bash
$ tox -e docs
-The resultant HTML will be the ``doc/build/html`` directory.
+The resultant HTML will be in the ``doc/build/html`` directory.
Release Notes
-------------
@@ -115,7 +139,7 @@ If any of the following applies to the patch, a release note is required:
* Current behavior is changed
* A security bug is fixed
-Reno is used to generate release notes. Please read the docs for details. In summary, use
+Reno is used to generate release notes. Use the commands:
.. code-block:: bash
@@ -123,7 +147,7 @@ Reno is used to generate release notes. Please read the docs for details. In sum
Then edit the sample file that was created and push it with your change.
-To see the results:
+To run the commands and see results:
.. code-block:: bash
@@ -131,7 +155,7 @@ To see the results:
$ tox -e releasenotes
-Then look at the generated release notes files in releasenotes/build/html in your favorite browser.
+At last, look at the generated release notes files in ``releasenotes/build/html`` in your browser.
Testing new code
----------------
@@ -149,3 +173,37 @@ or
.. code-block:: bash
$ pip install -e .
+
+Standardize Import Format
+=========================
+
+.. _`Import Order Guide`: http://docs.openstack.org/developer/hacking/#imports
+
+The import order shows below:
+
+* {{stdlib imports in human alphabetical order}}
+* \n
+* {{third-party lib imports in human alphabetical order}}
+* \n
+* {{project imports in human alphabetical order}}
+* \n
+* \n
+* {{begin your code}}
+
+Example
+~~~~~~~
+
+.. code-block:: python
+
+ import copy
+ import fixtures
+ import mock
+ import os
+
+ from osc_lib.api import auth
+ from osc_lib import utils
+ import six
+
+ from openstackclient import shell
+ from openstackclient.tests import utils
+
diff --git a/doc/source/humaninterfaceguide.rst b/doc/source/humaninterfaceguide.rst
index 9cca5aa..400ccfc 100644
--- a/doc/source/humaninterfaceguide.rst
+++ b/doc/source/humaninterfaceguide.rst
@@ -85,7 +85,7 @@ Simplicity
To best support new users and create straight forward interactions, designs
should be as simple as possible. When crafting new commands, designs should
minimize the amount of noise present in output: large amounts of
-nonessential data, overabundance of possible actions, etc. Designs should
+nonessential data, overabundance of possible actions and so on. Designs should
focus on the intent of the command, requiring only the necessary components
and either removing superfluous elements or making
them accessible through optional arguments. An example of this principle occurs
@@ -183,14 +183,6 @@ Output formats:
* user-friendly tables with headers, etc
* machine-parsable delimited
-Notes:
-
-* All long options names shall begin with two dashes ('--') and use a single dash
- ('-') internally between words (:code:`--like-this`). Underscores ('_') shall not
- be used in option names.
-* Authentication options conform to the common CLI authentication guidelines in
- :doc:`authentication`.
-
Global Options
~~~~~~~~~~~~~~
@@ -202,6 +194,16 @@ the command-line option takes priority. The environment variable names are deri
from the option name by dropping the leading dashes ('--'), converting each embedded
dash ('-') to an underscore ('_'), and converting to upper case.
+* Global options shall always have a long option name, certain common options may
+ also have short names. Short names should be reserved for global options to limit
+ the potential for duplication and multiple meanings between commands given the
+ limited set of available short names.
+* All long options names shall begin with two dashes ('--') and use a single dash
+ ('-') internally between words (:code:`--like-this`). Underscores ('_') shall not
+ be used in option names.
+* Authentication options conform to the common CLI authentication guidelines in
+ :doc:`authentication`.
+
For example, :code:`--os-username` can be set from the environment via
:code:`OS_USERNAME`.
@@ -245,6 +247,10 @@ Each command may have its own set of options distinct from the global options.
They follow the same style as the global options and always appear between
the command and any positional arguments the command requires.
+Command options shall only have long names. The small range of available
+short names makes it hard for a single short option name to have a consistent
+meaning across multiple commands.
+
Option Forms
++++++++++++
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 5889553..c8fc6f2 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -51,6 +51,7 @@ Developer Documentation
command-options
command-wrappers
command-errors
+ command-logs
specs/commands
Project Goals
diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst
index b23e7a3..4cb13e8 100644
--- a/doc/source/plugin-commands.rst
+++ b/doc/source/plugin-commands.rst
@@ -1,56 +1,113 @@
-=================
- Plugin Commands
-=================
+===============
+Plugin Commands
+===============
Note: To see the complete syntax for the plugin commands, see the `CLI_Ref`_
.. list-plugins:: openstack.cli.extension
-.. list-plugins:: openstack.key_manager.v1
+aodh
+----
+
+.. list-plugins:: openstack.alarming.v2
:detailed:
-.. list-plugins:: openstack.baremetal.v1
+barbican
+--------
+
+.. list-plugins:: openstack.key_manager.v1
:detailed:
+congress
+--------
+
.. list-plugins:: openstack.congressclient.v1
:detailed:
-.. list-plugins:: openstack.workflow_engine.v2
- :detailed:
+.. cue
+.. # cueclient is not in global-requirements
+.. # list-plugins:: openstack.mb.v1
+.. # :detailed:
-.. list-plugins:: openstack.data_processing.v1
- :detailed:
+designate
+---------
.. list-plugins:: openstack.dns.v1
:detailed:
-.. list-plugins:: openstack.management.v1
- :detailed:
+.. gnocchi
+.. # gnocchiclient is not in global-requirements
+.. # list-plugins:: openstack.metric.v1
+.. # :detailed:
-.. list-plugins:: openstack.messaging.v1
- :detailed:
+heat
+----
.. list-plugins:: openstack.orchestration.v1
:detailed:
-.. list-plugins:: openstack.search.v1
+ironic
+------
+
+.. list-plugins:: openstack.baremetal.v1
:detailed:
+ironic-inspector
+----------------
+
.. list-plugins:: openstack.baremetal_introspection.v1
:detailed:
+mistral
+-------
+
+.. list-plugins:: openstack.workflow_engine.v2
+ :detailed:
+
+murano
+------
+
.. list-plugins:: openstack.application_catalog.v1
:detailed:
+neutron
+-------
+
+.. list-plugins:: openstack.neutronclient.v2
+ :detailed:
+
+sahara
+------
+
+.. list-plugins:: openstack.data_processing.v1
+ :detailed:
+
+searchlight
+-----------
+
+.. list-plugins:: openstack.search.v1
+ :detailed:
+
+senlin
+------
+
.. list-plugins:: openstack.clustering.v1
:detailed:
+.. tripleo
.. # tripleoclient is not in global-requirements
-.. #.. list-plugins:: openstack.tripleoclient.v1
+.. # list-plugins:: openstack.tripleoclient.v1
.. # :detailed:
-.. # cueclient is not in global-requirements
-.. #.. list-plugins:: openstack.mb.v1
-.. # :detailed:
+.. watcher
+.. # watcherclient is not in global-requirements
+.. # list-plugins:: openstack.infra_optim.v1
+.. # :detailed:
+
+zaqar
+-----
+
+.. list-plugins:: openstack.messaging.v1
+ :detailed:
-.. _CLI_Ref: http://docs.openstack.org/cli-reference/openstack.html \ No newline at end of file
+.. _CLI_Ref: http://docs.openstack.org/cli-reference/openstack.html
diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst
index 5911b33..a19eea9 100644
--- a/doc/source/plugins.rst
+++ b/doc/source/plugins.rst
@@ -12,8 +12,8 @@ Adoption
OpenStackClient promises to provide first class support for the following
OpenStack services: Compute, Identity, Image, Object Storage, Block Storage
-and Network. These services are considered essential to any OpenStack
-deployment.
+and Network (core objects). These services are considered essential
+to any OpenStack deployment.
Other OpenStack services, such as Orchestration or Telemetry may create an
OpenStackClient plugin. The source code will not be hosted by
@@ -21,6 +21,8 @@ OpenStackClient.
The following is a list of projects that are an OpenStackClient plugin.
+- aodhclient
+- gnocchiclient\*\*
- python-barbicanclient
- python-congressclient
- python-cueclient\*\*
@@ -30,18 +32,20 @@ The following is a list of projects that are an OpenStackClient plugin.
- python-ironic-inspector-client
- python-mistralclient
- python-muranoclient
+- python-neutronclient\*\*\*
- python-saharaclient
- python-searchlightclient
- python-senlinclient
- python-tripleoclient\*\*
+- python-watcherclient\*\*
- python-zaqarclient
-\*\* Note that some clients are not listed in global-requirements
+\*\* Note that some clients are not listed in global-requirements.
+
+\*\*\* Project contains advanced network services.
The following is a list of projects that are not an OpenStackClient plugin.
-- aodhclient
-- gnocchiclient
- python-troveclient
- python-magnumclient
- python-ceilometerclient
@@ -95,7 +99,7 @@ so the version should not contain the leading 'v' character.
.. code-block:: python
- from openstackclient.common import utils
+ from osc_lib import utils
DEFAULT_API_VERSION = '1'
@@ -151,12 +155,12 @@ the plugin commands:
.. code-block:: python
- # OSC common interfaces available to plugins:
- from openstackclient.common import command
- from openstackclient.common import exceptions
- from openstackclient.common import parseractions
- from openstackclient.common import logs
- from openstackclient.common import utils
+ # osc-lib interfaces available to plugins:
+ from osc_lib.cli import parseractions
+ from osc_lib.command import command
+ from osc_lib import exceptions
+ from osc_lib import logs
+ from osc_lib import utils
class DeleteMypluginobject(command.Command):
@@ -165,7 +169,7 @@ the plugin commands:
...
def take_action(self, parsed_args):
- # Client manager interfaces are availble to plugins.
+ # Client manager interfaces are available to plugins.
# This includes the OSC clients created.
client_manager = self.app.client_manager
diff --git a/doc/source/specs/command-objects/example.rst b/doc/source/specs/command-objects/example.rst
index 6f8b058..f596978 100644
--- a/doc/source/specs/command-objects/example.rst
+++ b/doc/source/specs/command-objects/example.rst
@@ -38,7 +38,7 @@ Delete example(s)
.. describe:: <example>
- Example to delete (name or ID)
+ Example(s) to delete (name or ID)
example list
------------
diff --git a/doc/source/specs/network-topology.rst b/doc/source/specs/network-topology.rst
new file mode 100755
index 0000000..6f9c853
--- /dev/null
+++ b/doc/source/specs/network-topology.rst
@@ -0,0 +1,44 @@
+================
+network topology
+================
+
+A **network topology** shows a topological graph about
+devices which connect to the specific network. Also, it
+will return availability information for each individual
+device within the network as well. One other thing to note
+is that it is the intention for OSC to collect data from
+existing REST APIs
+
+Network v2
+
+network topology list
+---------------------
+
+List network topologies
+
+.. program:: network topology list
+.. code:: bash
+
+ os network topology list
+ [--project <project>]
+
+.. option:: --project <project>
+
+ List network topologies for given project
+ (name or ID)
+
+network topology show
+---------------------
+
+Show network topology details
+
+.. program:: network topology show
+.. code:: bash
+
+ os network topology show
+ <network>
+
+.. _network_topology_show-network
+.. describe:: <network>
+
+ Show network topology for a specific network (name or ID)
diff --git a/examples/common.py b/examples/common.py
index 6d48a8c..d472fe6 100755
--- a/examples/common.py
+++ b/examples/common.py
@@ -38,8 +38,7 @@ import sys
import traceback
from keystoneauth1 import session as ks_session
-
-from openstackclient.api import auth
+from osc_lib.api import auth
CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s'
diff --git a/examples/object_api.py b/examples/object_api.py
index 11b62da..577fc05 100755
--- a/examples/object_api.py
+++ b/examples/object_api.py
@@ -25,13 +25,11 @@ import logging
import sys
import common
-
+from os_client_config import config as cloud_config
from openstackclient.api import object_store_v1 as object_store
from openstackclient.identity import client as identity_client
-from os_client_config import config as cloud_config
-
LOG = logging.getLogger('')
diff --git a/examples/osc-lib.py b/examples/osc-lib.py
index abaa187..cc04bc7 100755
--- a/examples/osc-lib.py
+++ b/examples/osc-lib.py
@@ -26,11 +26,10 @@ import logging
import sys
import common
+from os_client_config import config as cloud_config
from openstackclient.common import clientmanager
-from os_client_config import config as cloud_config
-
LOG = logging.getLogger('')
diff --git a/functional/common/test.py b/functional/common/test.py
index 4361561..f7f0361 100644
--- a/functional/common/test.py
+++ b/functional/common/test.py
@@ -54,18 +54,13 @@ class TestCase(testtools.TestCase):
@classmethod
def get_openstack_configuration_value(cls, configuration):
- opts = cls.get_show_opts([configuration])
+ opts = cls.get_opts([configuration])
return cls.openstack('configuration show ' + opts)
@classmethod
- def get_show_opts(cls, fields=[]):
- return ' -f value ' + ' '.join(['-c ' + it for it in fields])
-
- @classmethod
- def get_list_opts(cls, headers=[]):
- opts = ' -f csv '
- opts = opts + ' '.join(['-c ' + it for it in headers])
- return opts
+ def get_opts(cls, fields, format='value'):
+ return ' -f {0} {1}'.format(format,
+ ' '.join(['-c ' + it for it in fields]))
@classmethod
def assertOutput(cls, expected, actual):
diff --git a/functional/tests/common/test_availability_zone.py b/functional/tests/common/test_availability_zone.py
index 9296db2..da8aad7 100644
--- a/functional/tests/common/test_availability_zone.py
+++ b/functional/tests/common/test_availability_zone.py
@@ -20,6 +20,6 @@ class AvailabilityZoneTests(test.TestCase):
DEFAULT_AZ_NAME = 'nova'
def test_availability_zone_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('availability zone list' + opts)
self.assertIn(self.DEFAULT_AZ_NAME, raw_output)
diff --git a/functional/tests/common/test_help.py b/functional/tests/common/test_help.py
index fcce5f9..7601c41 100644
--- a/functional/tests/common/test_help.py
+++ b/functional/tests/common/test_help.py
@@ -19,11 +19,12 @@ class HelpTests(test.TestCase):
SERVER_COMMANDS = [
('server add security group', 'Add security group to server'),
('server add volume', 'Add volume to server'),
+ ('server backup create', 'Create a server backup image'),
('server create', 'Create a new server'),
('server delete', 'Delete server(s)'),
('server dump create', 'Create a dump file in server(s)'),
('server image create',
- 'Create a new disk image from a running server'),
+ 'Create a new server disk image from an existing server'),
('server list', 'List servers'),
('server lock',
'Lock server(s). '
diff --git a/functional/tests/common/test_quota.py b/functional/tests/common/test_quota.py
index 0bc93db..0316de2 100644
--- a/functional/tests/common/test_quota.py
+++ b/functional/tests/common/test_quota.py
@@ -27,7 +27,7 @@ class QuotaTests(test.TestCase):
def test_quota_set(self):
self.openstack('quota set --instances 11 --volumes 11 --networks 11 '
+ self.PROJECT_NAME)
- opts = self.get_show_opts(self.EXPECTED_FIELDS)
+ opts = self.get_opts(self.EXPECTED_FIELDS)
raw_output = self.openstack('quota show ' + self.PROJECT_NAME + opts)
self.assertEqual("11\n11\n11\n", raw_output)
diff --git a/functional/tests/compute/v2/test_agent.py b/functional/tests/compute/v2/test_agent.py
index df7c21f..d432768 100644
--- a/functional/tests/compute/v2/test_agent.py
+++ b/functional/tests/compute/v2/test_agent.py
@@ -16,7 +16,7 @@ from functional.common import test
class ComputeAgentTests(test.TestCase):
- """Functional tests for compute agent. """
+ """Functional tests for compute agent."""
ID = None
MD5HASH = hashlib.md5().hexdigest()
@@ -31,12 +31,12 @@ class ComputeAgentTests(test.TestCase):
@classmethod
def setUpClass(cls):
- opts = cls.get_show_opts(cls.HEADERS)
- raw_output = cls.openstack('compute agent create '
- + cls.OS + ' ' + cls.ARCH + ' '
- + cls.VER + ' ' + cls.URL + ' '
- + cls.MD5HASH + ' ' + cls.HYPER + ' '
- + opts)
+ opts = cls.get_opts(cls.HEADERS)
+ raw_output = cls.openstack('compute agent create ' +
+ cls.OS + ' ' + cls.ARCH + ' ' +
+ cls.VER + ' ' + cls.URL + ' ' +
+ cls.MD5HASH + ' ' + cls.HYPER + ' ' +
+ opts)
# Get agent id because agent can only be deleted by ID
output_list = raw_output.split('\n', 1)
@@ -64,10 +64,11 @@ class ComputeAgentTests(test.TestCase):
url = "http://openstack"
md5hash = hashlib.md5().hexdigest()
- raw_output = self.openstack('compute agent set '
- + self.ID + ' ' + ver + ' '
- + url + ' ' + md5hash)
- self.assertEqual('', raw_output)
+ self.openstack('compute agent set '
+ + self.ID
+ + ' --agent-version ' + ver
+ + ' --url ' + url
+ + ' --md5hash ' + md5hash)
raw_output = self.openstack('compute agent list')
self.assertIn(self.ID, raw_output)
diff --git a/functional/tests/compute/v2/test_aggregate.py b/functional/tests/compute/v2/test_aggregate.py
index b4d27fa..adb14e5 100644
--- a/functional/tests/compute/v2/test_aggregate.py
+++ b/functional/tests/compute/v2/test_aggregate.py
@@ -16,7 +16,7 @@ from functional.common import test
class AggregateTests(test.TestCase):
- """Functional tests for aggregate. """
+ """Functional tests for aggregate."""
NAME = uuid.uuid4().hex
HEADERS = ['Name']
@@ -24,7 +24,7 @@ class AggregateTests(test.TestCase):
@classmethod
def setUpClass(cls):
- opts = cls.get_show_opts(cls.FIELDS)
+ opts = cls.get_opts(cls.FIELDS)
# Use the default 'nova' availability zone for the aggregate.
raw_output = cls.openstack(
'aggregate create --zone nova ' + cls.NAME + opts
@@ -38,17 +38,17 @@ class AggregateTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_aggregate_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('aggregate list' + opts)
self.assertIn(self.NAME, raw_output)
def test_aggregate_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('aggregate show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
def test_aggregate_properties(self):
- opts = self.get_show_opts(['properties'])
+ opts = self.get_opts(['properties'])
raw_output = self.openstack(
'aggregate set --property a=b --property c=d ' + self.NAME
@@ -57,3 +57,11 @@ class AggregateTests(test.TestCase):
raw_output = self.openstack('aggregate show ' + self.NAME + opts)
self.assertIn("a='b', c='d'\n", raw_output)
+
+ raw_output = self.openstack(
+ 'aggregate unset --property a ' + self.NAME
+ )
+ self.assertEqual('', raw_output)
+
+ raw_output = self.openstack('aggregate show ' + self.NAME + opts)
+ self.assertIn("c='d'\n", raw_output)
diff --git a/functional/tests/compute/v2/test_flavor.py b/functional/tests/compute/v2/test_flavor.py
index d1f5f95..ef0d2fe 100644
--- a/functional/tests/compute/v2/test_flavor.py
+++ b/functional/tests/compute/v2/test_flavor.py
@@ -16,7 +16,7 @@ from functional.common import test
class FlavorTests(test.TestCase):
- """Functional tests for flavor. """
+ """Functional tests for flavor."""
NAME = uuid.uuid4().hex
HEADERS = ['Name']
@@ -24,8 +24,9 @@ class FlavorTests(test.TestCase):
@classmethod
def setUpClass(cls):
- opts = cls.get_show_opts(cls.FIELDS)
- raw_output = cls.openstack('flavor create ' + cls.NAME + opts)
+ opts = cls.get_opts(cls.FIELDS)
+ raw_output = cls.openstack(
+ 'flavor create --property a=b --property c=d ' + cls.NAME + opts)
expected = cls.NAME + '\n'
cls.assertOutput(expected, raw_output)
@@ -35,31 +36,34 @@ class FlavorTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_flavor_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('flavor list' + opts)
self.assertIn("small", raw_output)
self.assertIn(self.NAME, raw_output)
def test_flavor_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('flavor show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
def test_flavor_properties(self):
- opts = self.get_show_opts(['properties'])
+ opts = self.get_opts(['properties'])
+ # check the properties we added in create command.
+ raw_output = self.openstack('flavor show ' + self.NAME + opts)
+ self.assertEqual("a='b', c='d'\n", raw_output)
raw_output = self.openstack(
- 'flavor set --property a=b --property c=d ' + self.NAME
+ 'flavor set --property e=f --property g=h ' + self.NAME
)
self.assertEqual('', raw_output)
raw_output = self.openstack('flavor show ' + self.NAME + opts)
- self.assertEqual("a='b', c='d'\n", raw_output)
+ self.assertEqual("a='b', c='d', e='f', g='h'\n", raw_output)
raw_output = self.openstack(
- 'flavor unset --property a ' + self.NAME
+ 'flavor unset --property a --property c ' + self.NAME
)
self.assertEqual('', raw_output)
raw_output = self.openstack('flavor show ' + self.NAME + opts)
- self.assertEqual("c='d'\n", raw_output)
+ self.assertEqual("e='f', g='h'\n", raw_output)
diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py
index 96c1c1a..a9d0e9c 100644
--- a/functional/tests/compute/v2/test_server.py
+++ b/functional/tests/compute/v2/test_server.py
@@ -23,19 +23,27 @@ class ServerTests(test.TestCase):
@classmethod
def get_flavor(cls):
- # NOTE(rtheis): Get m1.tiny flavor since functional tests may
- # create other flavors.
- raw_output = cls.openstack('flavor show m1.tiny -c id -f value')
- return raw_output.strip('\n')
+ # NOTE(rtheis): Get cirros256 or m1.tiny flavors since functional
+ # tests may create other flavors.
+ flavors = cls.openstack('flavor list -c Name -f value').split('\n')
+ server_flavor = None
+ for flavor in flavors:
+ if flavor in ['m1.tiny', 'cirros256']:
+ server_flavor = flavor
+ break
+ return server_flavor
@classmethod
def get_image(cls):
- # NOTE(rtheis): Get public images since functional tests may
- # create private images.
- raw_output = cls.openstack('image list --public -f value -c ID')
- ray = raw_output.split('\n')
- idx = int(len(ray) / 2)
- return ray[idx]
+ # NOTE(rtheis): Get cirros image since functional tests may
+ # create other images.
+ images = cls.openstack('image list -c Name -f value').split('\n')
+ server_image = None
+ for image in images:
+ if image.startswith('cirros-') and image.endswith('-uec'):
+ server_image = image
+ break
+ return server_image
@classmethod
def get_network(cls):
@@ -50,7 +58,7 @@ class ServerTests(test.TestCase):
def server_create(self, name=None):
"""Create server. Add cleanup."""
name = name or data_utils.rand_uuid()
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
flavor = self.get_flavor()
image = self.get_image()
network = self.get_network()
@@ -64,7 +72,7 @@ class ServerTests(test.TestCase):
def server_list(self, params=[]):
"""List servers."""
- opts = self.get_list_opts(params)
+ opts = self.get_opts(params)
return self.openstack('server list' + opts)
def server_delete(self, name):
@@ -106,7 +114,7 @@ class ServerTests(test.TestCase):
2) List servers
3) Check output
"""
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('server list' + opts)
self.assertIn(self.NAME, raw_output)
@@ -118,7 +126,7 @@ class ServerTests(test.TestCase):
2) Show server
3) Check output
"""
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('server show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
@@ -136,13 +144,13 @@ class ServerTests(test.TestCase):
# metadata
raw_output = self.openstack(
'server set --property a=b --property c=d ' + self.NAME)
- opts = self.get_show_opts(["name", "properties"])
+ opts = self.get_opts(["name", "properties"])
raw_output = self.openstack('server show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output)
raw_output = self.openstack(
'server unset --property a ' + self.NAME)
- opts = self.get_show_opts(["name", "properties"])
+ opts = self.get_opts(["name", "properties"])
raw_output = self.openstack('server show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\nc='d'\n", raw_output)
@@ -216,7 +224,7 @@ class ServerTests(test.TestCase):
"""
self.wait_for_status("ACTIVE")
# rescue
- opts = self.get_show_opts(["adminPass"])
+ opts = self.get_opts(["adminPass"])
raw_output = self.openstack('server rescue ' + self.NAME + opts)
self.assertNotEqual("", raw_output)
self.wait_for_status("RESCUE")
@@ -240,25 +248,26 @@ class ServerTests(test.TestCase):
"""
self.wait_for_status("ACTIVE")
# attach ip
- opts = self.get_show_opts(["id", "floating_ip_address"])
- raw_output = self.openstack('ip floating create ' +
+ opts = self.get_opts(["id", "floating_ip_address"])
+ raw_output = self.openstack('floating ip create ' +
self.IP_POOL +
opts)
ip, ipid, rol = tuple(raw_output.split('\n'))
self.assertNotEqual("", ipid)
self.assertNotEqual("", ip)
- raw_output = self.openstack('ip floating add ' + ip + ' ' + self.NAME)
+ raw_output = self.openstack('server add floating ip ' + self.NAME +
+ ' ' + ip)
self.assertEqual("", raw_output)
raw_output = self.openstack('server show ' + self.NAME)
self.assertIn(ip, raw_output)
# detach ip
- raw_output = self.openstack('ip floating remove ' + ip + ' ' +
- self.NAME)
+ raw_output = self.openstack('server remove floating ip ' + self.NAME +
+ ' ' + ip)
self.assertEqual("", raw_output)
raw_output = self.openstack('server show ' + self.NAME)
self.assertNotIn(ip, raw_output)
- raw_output = self.openstack('ip floating delete ' + ipid)
+ raw_output = self.openstack('floating ip delete ' + ipid)
self.assertEqual("", raw_output)
def test_server_reboot(self):
@@ -280,7 +289,7 @@ class ServerTests(test.TestCase):
# TODO(thowe): Add a server wait command to osc
failures = ['ERROR']
total_sleep = 0
- opts = self.get_show_opts(['status'])
+ opts = self.get_opts(['status'])
while total_sleep < wait:
status = self.openstack('server show ' + self.NAME + opts)
status = status.rstrip()
diff --git a/functional/tests/compute/v2/test_server_group.py b/functional/tests/compute/v2/test_server_group.py
index ce4f97a..b912605 100644
--- a/functional/tests/compute/v2/test_server_group.py
+++ b/functional/tests/compute/v2/test_server_group.py
@@ -16,7 +16,7 @@ from functional.common import test
class ServerGroupTests(test.TestCase):
- """Functional tests for servergroup. """
+ """Functional tests for servergroup."""
NAME = uuid.uuid4().hex
HEADERS = ['Name']
@@ -24,7 +24,7 @@ class ServerGroupTests(test.TestCase):
@classmethod
def setUpClass(cls):
- opts = cls.get_show_opts(cls.FIELDS)
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack('server group create --policy affinity ' +
cls.NAME + opts)
expected = cls.NAME + '\n'
@@ -36,11 +36,11 @@ class ServerGroupTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_server_group_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('server group list' + opts)
self.assertIn(self.NAME, raw_output)
def test_server_group_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('server group show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
diff --git a/functional/tests/identity/v2/test_identity.py b/functional/tests/identity/v2/common.py
index 9adbe49..ca061a4 100644
--- a/functional/tests/identity/v2/test_identity.py
+++ b/functional/tests/identity/v2/common.py
@@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import os
+
from tempest.lib.common.utils import data_utils
from functional.common import test
@@ -39,8 +41,11 @@ class IdentityTests(test.TestCase):
@classmethod
def setUpClass(cls):
- if hasattr(super(IdentityTests, cls), 'setUpClass'):
- super(IdentityTests, cls).setUpClass()
+ # prepare v2 env
+ os.environ['OS_IDENTITY_API_VERSION'] = '2.0'
+ auth_url = os.environ.get('OS_AUTH_URL')
+ auth_url = auth_url.replace('v3', 'v2.0')
+ os.environ['OS_AUTH_URL'] = auth_url
# create dummy project
cls.project_name = data_utils.rand_name('TestProject')
@@ -56,9 +61,6 @@ class IdentityTests(test.TestCase):
def tearDownClass(cls):
cls.openstack('project delete %s' % cls.project_name)
- if hasattr(super(IdentityTests, cls), 'tearDownClass'):
- super(IdentityTests, cls).tearDownClass()
-
def _create_dummy_project(self, add_clean_up=True):
project_name = data_utils.rand_name('TestProject')
project_description = data_utils.rand_name('description')
diff --git a/functional/tests/identity/v2/test_catalog.py b/functional/tests/identity/v2/test_catalog.py
index 3a1f7e1..b6291e0 100644
--- a/functional/tests/identity/v2/test_catalog.py
+++ b/functional/tests/identity/v2/test_catalog.py
@@ -10,10 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from functional.tests.identity.v2 import test_identity
+from functional.tests.identity.v2 import common
-class CatalogTests(test_identity.IdentityTests):
+class CatalogTests(common.IdentityTests):
def test_catalog_list(self):
raw_output = self.openstack('catalog list')
diff --git a/functional/tests/identity/v2/test_ec2_credentials.py b/functional/tests/identity/v2/test_ec2_credentials.py
index 86702c0..7a8ee35 100644
--- a/functional/tests/identity/v2/test_ec2_credentials.py
+++ b/functional/tests/identity/v2/test_ec2_credentials.py
@@ -10,10 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from functional.tests.identity.v2 import test_identity
+from functional.tests.identity.v2 import common
-class EC2CredentialsTests(test_identity.IdentityTests):
+class EC2CredentialsTests(common.IdentityTests):
def test_ec2_credentials_create(self):
self._create_dummy_ec2_credentials()
@@ -25,6 +25,14 @@ class EC2CredentialsTests(test_identity.IdentityTests):
)
self.assertEqual(0, len(raw_output))
+ def test_ec2_credentials_multi_delete(self):
+ access_key_1 = self._create_dummy_ec2_credentials(add_clean_up=False)
+ access_key_2 = self._create_dummy_ec2_credentials(add_clean_up=False)
+ raw_output = self.openstack(
+ 'ec2 credentials delete ' + access_key_1 + ' ' + access_key_2
+ )
+ self.assertEqual(0, len(raw_output))
+
def test_ec2_credentials_list(self):
self._create_dummy_ec2_credentials()
raw_output = self.openstack('ec2 credentials list')
diff --git a/functional/tests/identity/v2/test_endpoint.py b/functional/tests/identity/v2/test_endpoint.py
index 8064365..34888c0 100644
--- a/functional/tests/identity/v2/test_endpoint.py
+++ b/functional/tests/identity/v2/test_endpoint.py
@@ -10,10 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from functional.tests.identity.v2 import test_identity
+from functional.tests.identity.v2 import common
-class EndpointTests(test_identity.IdentityTests):
+class EndpointTests(common.IdentityTests):
def test_endpoint_create(self):
self._create_dummy_endpoint()
@@ -24,6 +24,13 @@ class EndpointTests(test_identity.IdentityTests):
'endpoint delete %s' % endpoint_id)
self.assertEqual(0, len(raw_output))
+ def test_endpoint_multi_delete(self):
+ endpoint_id_1 = self._create_dummy_endpoint(add_clean_up=False)
+ endpoint_id_2 = self._create_dummy_endpoint(add_clean_up=False)
+ raw_output = self.openstack(
+ 'endpoint delete ' + endpoint_id_1 + ' ' + endpoint_id_2)
+ self.assertEqual(0, len(raw_output))
+
def test_endpoint_list(self):
endpoint_id = self._create_dummy_endpoint()
raw_output = self.openstack('endpoint list')
diff --git a/functional/tests/identity/v2/test_project.py b/functional/tests/identity/v2/test_project.py
index e9580ec..7fb1a98 100644
--- a/functional/tests/identity/v2/test_project.py
+++ b/functional/tests/identity/v2/test_project.py
@@ -12,10 +12,10 @@
from tempest.lib.common.utils import data_utils
-from functional.tests.identity.v2 import test_identity
+from functional.tests.identity.v2 import common
-class ProjectTests(test_identity.IdentityTests):
+class ProjectTests(common.IdentityTests):
def test_project_create(self):
project_name = data_utils.rand_name('TestProject')
@@ -49,7 +49,7 @@ class ProjectTests(test_identity.IdentityTests):
def test_project_list(self):
raw_output = self.openstack('project list')
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
def test_project_set(self):
project_name = self._create_dummy_project()
diff --git a/functional/tests/identity/v2/test_role.py b/functional/tests/identity/v2/test_role.py
index 9ee6006..0f8d5ed 100644
--- a/functional/tests/identity/v2/test_role.py
+++ b/functional/tests/identity/v2/test_role.py
@@ -10,10 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from functional.tests.identity.v2 import test_identity
+from functional.tests.identity.v2 import common
-class RoleTests(test_identity.IdentityTests):
+class RoleTests(common.IdentityTests):
def test_role_create(self):
self._create_dummy_role()
@@ -27,7 +27,7 @@ class RoleTests(test_identity.IdentityTests):
self._create_dummy_role()
raw_output = self.openstack('role list')
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
def test_role_list_with_user_project(self):
project_name = self._create_dummy_project()
@@ -58,7 +58,7 @@ class RoleTests(test_identity.IdentityTests):
'' % {'project': project_name,
'user': username})
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
self.assertEqual(1, len(items))
def test_role_show(self):
diff --git a/functional/tests/identity/v2/test_service.py b/functional/tests/identity/v2/test_service.py
index bd982be..9dcb6be 100644
--- a/functional/tests/identity/v2/test_service.py
+++ b/functional/tests/identity/v2/test_service.py
@@ -10,10 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from functional.tests.identity.v2 import test_identity
+from functional.tests.identity.v2 import common
-class ServiceTests(test_identity.IdentityTests):
+class ServiceTests(common.IdentityTests):
def test_service_create(self):
self._create_dummy_service()
@@ -23,11 +23,18 @@ class ServiceTests(test_identity.IdentityTests):
raw_output = self.openstack('service delete %s' % service_name)
self.assertEqual(0, len(raw_output))
+ def test_service_multi_delete(self):
+ service_name_1 = self._create_dummy_service(add_clean_up=False)
+ service_name_2 = self._create_dummy_service(add_clean_up=False)
+ raw_output = self.openstack(
+ 'service delete ' + service_name_1 + ' ' + service_name_2)
+ self.assertEqual(0, len(raw_output))
+
def test_service_list(self):
self._create_dummy_service()
raw_output = self.openstack('service list')
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
def test_service_show(self):
service_name = self._create_dummy_service()
diff --git a/functional/tests/identity/v2/test_token.py b/functional/tests/identity/v2/test_token.py
index bac2b0a..ca9b7d6 100644
--- a/functional/tests/identity/v2/test_token.py
+++ b/functional/tests/identity/v2/test_token.py
@@ -10,10 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from functional.tests.identity.v2 import test_identity
+from functional.tests.identity.v2 import common
-class TokenTests(test_identity.IdentityTests):
+class TokenTests(common.IdentityTests):
def test_token_issue(self):
self._create_dummy_token()
diff --git a/functional/tests/identity/v2/test_user.py b/functional/tests/identity/v2/test_user.py
index 34cabf7..ef4defa 100644
--- a/functional/tests/identity/v2/test_user.py
+++ b/functional/tests/identity/v2/test_user.py
@@ -13,10 +13,10 @@
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions
-from functional.tests.identity.v2 import test_identity
+from functional.tests.identity.v2 import common
-class UserTests(test_identity.IdentityTests):
+class UserTests(common.IdentityTests):
def test_user_create(self):
self._create_dummy_user()
@@ -29,7 +29,7 @@ class UserTests(test_identity.IdentityTests):
def test_user_list(self):
raw_output = self.openstack('user list')
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
def test_user_set(self):
username = self._create_dummy_user()
diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/common.py
index 3f98874..47019c5 100644
--- a/functional/tests/identity/v3/test_identity.py
+++ b/functional/tests/identity/v3/common.py
@@ -27,7 +27,8 @@ class IdentityTests(test.TestCase):
GROUP_FIELDS = ['description', 'domain_id', 'id', 'name', 'links']
TOKEN_FIELDS = ['expires', 'id', 'project_id', 'user_id']
USER_FIELDS = ['email', 'enabled', 'id', 'name', 'name',
- 'domain_id', 'default_project_id', 'description']
+ 'domain_id', 'default_project_id', 'description',
+ 'password_expires_at']
PROJECT_FIELDS = ['description', 'id', 'domain_id', 'is_domain',
'enabled', 'name', 'parent_id', 'links']
ROLE_FIELDS = ['id', 'name', 'links', 'domain_id']
@@ -51,15 +52,11 @@ class IdentityTests(test.TestCase):
@classmethod
def setUpClass(cls):
- if hasattr(super(IdentityTests, cls), 'setUpClass'):
- super(IdentityTests, cls).setUpClass()
-
# prepare v3 env
+ os.environ['OS_IDENTITY_API_VERSION'] = '3'
auth_url = os.environ.get('OS_AUTH_URL')
auth_url = auth_url.replace('v2.0', 'v3')
os.environ['OS_AUTH_URL'] = auth_url
- os.environ['OS_IDENTITY_API_VERSION'] = '3'
- os.environ['OS_AUTH_TYPE'] = 'v3password'
# create dummy domain
cls.domain_name = data_utils.rand_name('TestDomain')
@@ -91,9 +88,6 @@ class IdentityTests(test.TestCase):
cls.openstack('domain set --disable %s' % cls.domain_name)
cls.openstack('domain delete %s' % cls.domain_name)
- if hasattr(super(IdentityTests, cls), 'tearDownClass'):
- super(IdentityTests, cls).tearDownClass()
-
def _create_dummy_user(self, add_clean_up=True):
username = data_utils.rand_name('TestUser')
password = data_utils.rand_name('password')
diff --git a/functional/tests/identity/v3/test_catalog.py b/functional/tests/identity/v3/test_catalog.py
index 6836452..e33876b 100644
--- a/functional/tests/identity/v3/test_catalog.py
+++ b/functional/tests/identity/v3/test_catalog.py
@@ -10,10 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from functional.tests.identity.v3 import test_identity
+from functional.tests.identity.v3 import common
-class CatalogTests(test_identity.IdentityTests):
+class CatalogTests(common.IdentityTests):
def test_catalog_list(self):
raw_output = self.openstack('catalog list')
diff --git a/functional/tests/identity/v3/test_domain.py b/functional/tests/identity/v3/test_domain.py
index 0708e42..3f514b5 100644
--- a/functional/tests/identity/v3/test_domain.py
+++ b/functional/tests/identity/v3/test_domain.py
@@ -13,10 +13,10 @@
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions
-from functional.tests.identity.v3 import test_identity
+from functional.tests.identity.v3 import common
-class DomainTests(test_identity.IdentityTests):
+class DomainTests(common.IdentityTests):
def test_domain_create(self):
domain_name = data_utils.rand_name('TestDomain')
@@ -33,7 +33,7 @@ class DomainTests(test_identity.IdentityTests):
self._create_dummy_domain()
raw_output = self.openstack('domain list')
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
def test_domain_delete(self):
domain_name = self._create_dummy_domain(add_clean_up=False)
@@ -43,6 +43,18 @@ class DomainTests(test_identity.IdentityTests):
raw_output = self.openstack('domain delete %s' % domain_name)
self.assertEqual(0, len(raw_output))
+ def test_domain_multi_delete(self):
+ domain_1 = self._create_dummy_domain(add_clean_up=False)
+ domain_2 = self._create_dummy_domain(add_clean_up=False)
+ # cannot delete enabled domain, disable it first
+ raw_output = self.openstack('domain set --disable %s' % domain_1)
+ self.assertEqual(0, len(raw_output))
+ raw_output = self.openstack('domain set --disable %s' % domain_2)
+ self.assertEqual(0, len(raw_output))
+ raw_output = self.openstack(
+ 'domain delete %s %s' % (domain_1, domain_2))
+ self.assertEqual(0, len(raw_output))
+
def test_domain_delete_failure(self):
domain_name = self._create_dummy_domain()
# cannot delete enabled domain
diff --git a/functional/tests/identity/v3/test_endpoint.py b/functional/tests/identity/v3/test_endpoint.py
index ec11dea..e0afab2 100644
--- a/functional/tests/identity/v3/test_endpoint.py
+++ b/functional/tests/identity/v3/test_endpoint.py
@@ -12,10 +12,10 @@
from tempest.lib.common.utils import data_utils
-from functional.tests.identity.v3 import test_identity
+from functional.tests.identity.v3 import common
-class EndpointTests(test_identity.IdentityTests):
+class EndpointTests(common.IdentityTests):
def test_endpoint_create(self):
self._create_dummy_endpoint(interface='public')
@@ -28,6 +28,13 @@ class EndpointTests(test_identity.IdentityTests):
'endpoint delete %s' % endpoint_id)
self.assertEqual(0, len(raw_output))
+ def test_endpoint_multi_delete(self):
+ endpoint_1 = self._create_dummy_endpoint(add_clean_up=False)
+ endpoint_2 = self._create_dummy_endpoint(add_clean_up=False)
+ raw_output = self.openstack(
+ 'endpoint delete %s %s' % (endpoint_1, endpoint_2))
+ self.assertEqual(0, len(raw_output))
+
def test_endpoint_list(self):
endpoint_id = self._create_dummy_endpoint()
raw_output = self.openstack('endpoint list')
diff --git a/functional/tests/identity/v3/test_group.py b/functional/tests/identity/v3/test_group.py
index 3f58864..3507569 100644
--- a/functional/tests/identity/v3/test_group.py
+++ b/functional/tests/identity/v3/test_group.py
@@ -12,10 +12,10 @@
from tempest.lib.common.utils import data_utils
-from functional.tests.identity.v3 import test_identity
+from functional.tests.identity.v3 import common
-class GroupTests(test_identity.IdentityTests):
+class GroupTests(common.IdentityTests):
def test_group_create(self):
self._create_dummy_group()
@@ -24,7 +24,7 @@ class GroupTests(test_identity.IdentityTests):
group_name = self._create_dummy_group()
raw_output = self.openstack('group list')
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
self.assertIn(group_name, raw_output)
def test_group_list_with_domain(self):
@@ -32,7 +32,7 @@ class GroupTests(test_identity.IdentityTests):
raw_output = self.openstack(
'group list --domain %s' % self.domain_name)
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
self.assertIn(group_name, raw_output)
def test_group_delete(self):
diff --git a/functional/tests/identity/v3/test_idp.py b/functional/tests/identity/v3/test_idp.py
index 08f660f..bc9690f 100644
--- a/functional/tests/identity/v3/test_idp.py
+++ b/functional/tests/identity/v3/test_idp.py
@@ -10,11 +10,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from functional.tests.identity.v3 import test_identity
+from functional.tests.identity.v3 import common
from tempest.lib.common.utils import data_utils
-class IdentityProviderTests(test_identity.IdentityTests):
+class IdentityProviderTests(common.IdentityTests):
# Introduce functional test case for command 'Identity Provider'
def test_idp_create(self):
@@ -26,6 +26,13 @@ class IdentityProviderTests(test_identity.IdentityTests):
% identity_provider)
self.assertEqual(0, len(raw_output))
+ def test_idp_multi_delete(self):
+ idp_1 = self._create_dummy_idp(add_clean_up=False)
+ idp_2 = self._create_dummy_idp(add_clean_up=False)
+ raw_output = self.openstack(
+ 'identity provider delete %s %s' % (idp_1, idp_2))
+ self.assertEqual(0, len(raw_output))
+
def test_idp_show(self):
identity_provider = self._create_dummy_idp(add_clean_up=True)
raw_output = self.openstack('identity provider show %s'
diff --git a/functional/tests/identity/v3/test_project.py b/functional/tests/identity/v3/test_project.py
index d060c7b..a27c58f 100644
--- a/functional/tests/identity/v3/test_project.py
+++ b/functional/tests/identity/v3/test_project.py
@@ -12,10 +12,10 @@
from tempest.lib.common.utils import data_utils
-from functional.tests.identity.v3 import test_identity
+from functional.tests.identity.v3 import common
-class ProjectTests(test_identity.IdentityTests):
+class ProjectTests(common.IdentityTests):
def test_project_create(self):
project_name = data_utils.rand_name('TestProject')
@@ -57,14 +57,14 @@ class ProjectTests(test_identity.IdentityTests):
def test_project_list(self):
raw_output = self.openstack('project list')
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
def test_project_list_with_domain(self):
project_name = self._create_dummy_project()
raw_output = self.openstack(
'project list --domain %s' % self.domain_name)
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
self.assertIn(project_name, raw_output)
self.assertTrue(len(items) > 0)
diff --git a/functional/tests/identity/v3/test_region.py b/functional/tests/identity/v3/test_region.py
index be6ef1c..2ebc0e5 100644
--- a/functional/tests/identity/v3/test_region.py
+++ b/functional/tests/identity/v3/test_region.py
@@ -10,10 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from functional.tests.identity.v3 import test_identity
+from functional.tests.identity.v3 import common
-class RegionTests(test_identity.IdentityTests):
+class RegionTests(common.IdentityTests):
def test_region_create(self):
self._create_dummy_region()
@@ -27,6 +27,13 @@ class RegionTests(test_identity.IdentityTests):
raw_output = self.openstack('region delete %s' % region_id)
self.assertEqual(0, len(raw_output))
+ def test_region_multi_delete(self):
+ region_1 = self._create_dummy_region(add_clean_up=False)
+ region_2 = self._create_dummy_region(add_clean_up=False)
+ raw_output = self.openstack(
+ 'region delete %s %s' % (region_1, region_2))
+ self.assertEqual(0, len(raw_output))
+
def test_region_list(self):
raw_output = self.openstack('region list')
items = self.parse_listing(raw_output)
diff --git a/functional/tests/identity/v3/test_role.py b/functional/tests/identity/v3/test_role.py
index 29dc4b2..60aaf3f 100644
--- a/functional/tests/identity/v3/test_role.py
+++ b/functional/tests/identity/v3/test_role.py
@@ -12,10 +12,10 @@
from tempest.lib.common.utils import data_utils
-from functional.tests.identity.v3 import test_identity
+from functional.tests.identity.v3 import common
-class RoleTests(test_identity.IdentityTests):
+class RoleTests(common.IdentityTests):
def test_role_create(self):
self._create_dummy_role()
@@ -29,7 +29,7 @@ class RoleTests(test_identity.IdentityTests):
self._create_dummy_role()
raw_output = self.openstack('role list')
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
def test_role_list_with_user_project(self):
role_name = self._create_dummy_role()
@@ -69,7 +69,7 @@ class RoleTests(test_identity.IdentityTests):
'user': username,
'user_domain': self.domain_name})
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
self.assertEqual(1, len(items))
def test_role_show(self):
diff --git a/functional/tests/identity/v3/test_service.py b/functional/tests/identity/v3/test_service.py
index 684fa5f..79a63dc 100644
--- a/functional/tests/identity/v3/test_service.py
+++ b/functional/tests/identity/v3/test_service.py
@@ -12,10 +12,10 @@
from tempest.lib.common.utils import data_utils
-from functional.tests.identity.v3 import test_identity
+from functional.tests.identity.v3 import common
-class ServiceTests(test_identity.IdentityTests):
+class ServiceTests(common.IdentityTests):
def test_service_create(self):
self._create_dummy_service()
@@ -25,11 +25,18 @@ class ServiceTests(test_identity.IdentityTests):
raw_output = self.openstack('service delete %s' % service_name)
self.assertEqual(0, len(raw_output))
+ def test_service_multi_delete(self):
+ service_1 = self._create_dummy_service(add_clean_up=False)
+ service_2 = self._create_dummy_service(add_clean_up=False)
+ raw_output = self.openstack(
+ 'service delete %s %s' % (service_1, service_2))
+ self.assertEqual(0, len(raw_output))
+
def test_service_list(self):
self._create_dummy_service()
raw_output = self.openstack('service list')
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
def test_service_set(self):
service_name = self._create_dummy_service()
diff --git a/functional/tests/identity/v3/test_service_provider.py b/functional/tests/identity/v3/test_service_provider.py
index 936c662..458c2ae 100644
--- a/functional/tests/identity/v3/test_service_provider.py
+++ b/functional/tests/identity/v3/test_service_provider.py
@@ -10,11 +10,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from functional.tests.identity.v3 import test_identity
+from functional.tests.identity.v3 import common
from tempest.lib.common.utils import data_utils
-class ServiceProviderTests(test_identity.IdentityTests):
+class ServiceProviderTests(common.IdentityTests):
# Introduce functional test cases for command 'Service Provider'
def test_sp_create(self):
@@ -26,6 +26,13 @@ class ServiceProviderTests(test_identity.IdentityTests):
% service_provider)
self.assertEqual(0, len(raw_output))
+ def test_sp_multi_delete(self):
+ sp1 = self._create_dummy_sp(add_clean_up=False)
+ sp2 = self._create_dummy_sp(add_clean_up=False)
+ raw_output = self.openstack(
+ 'service provider delete %s %s' % (sp1, sp2))
+ self.assertEqual(0, len(raw_output))
+
def test_sp_show(self):
service_provider = self._create_dummy_sp(add_clean_up=True)
raw_output = self.openstack('service provider show %s'
diff --git a/functional/tests/identity/v3/test_token.py b/functional/tests/identity/v3/test_token.py
index 67fddcc..d8d3f43 100644
--- a/functional/tests/identity/v3/test_token.py
+++ b/functional/tests/identity/v3/test_token.py
@@ -10,10 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from functional.tests.identity.v3 import test_identity
+from functional.tests.identity.v3 import common
-class TokenTests(test_identity.IdentityTests):
+class TokenTests(common.IdentityTests):
def test_token_issue(self):
raw_output = self.openstack('token issue')
diff --git a/functional/tests/identity/v3/test_user.py b/functional/tests/identity/v3/test_user.py
index cc3e08a..f365706 100644
--- a/functional/tests/identity/v3/test_user.py
+++ b/functional/tests/identity/v3/test_user.py
@@ -12,10 +12,10 @@
from tempest.lib.common.utils import data_utils
-from functional.tests.identity.v3 import test_identity
+from functional.tests.identity.v3 import common
-class UserTests(test_identity.IdentityTests):
+class UserTests(common.IdentityTests):
def test_user_create(self):
self._create_dummy_user()
@@ -31,7 +31,7 @@ class UserTests(test_identity.IdentityTests):
def test_user_list(self):
raw_output = self.openstack('user list')
items = self.parse_listing(raw_output)
- self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS)
+ self.assert_table_structure(items, common.BASIC_LIST_HEADERS)
def test_user_set(self):
username = self._create_dummy_user()
diff --git a/functional/tests/image/v1/test_image.py b/functional/tests/image/v1/test_image.py
index fe61f83..c64c0d9 100644
--- a/functional/tests/image/v1/test_image.py
+++ b/functional/tests/image/v1/test_image.py
@@ -27,7 +27,7 @@ class ImageTests(test.TestCase):
@classmethod
def setUpClass(cls):
os.environ['OS_IMAGE_API_VERSION'] = '1'
- opts = cls.get_show_opts(cls.FIELDS)
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack('image create ' + cls.NAME + opts)
expected = cls.NAME + '\n'
cls.assertOutput(expected, raw_output)
@@ -43,17 +43,17 @@ class ImageTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_image_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('image list' + opts)
self.assertIn(self.NAME, raw_output)
def test_image_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('image show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
def test_image_set(self):
- opts = self.get_show_opts([
+ opts = self.get_opts([
"disk_format", "is_public", "min_disk", "min_ram", "name"])
self.openstack('image set --min-disk 4 --min-ram 5 ' +
'--disk-format qcow2 --public ' + self.NAME)
@@ -61,7 +61,7 @@ class ImageTests(test.TestCase):
self.assertEqual("qcow2\nTrue\n4\n5\n" + self.NAME + '\n', raw_output)
def test_image_metadata(self):
- opts = self.get_show_opts(["name", "properties"])
+ opts = self.get_opts(["name", "properties"])
self.openstack('image set --property a=b --property c=d ' + self.NAME)
raw_output = self.openstack('image show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output)
diff --git a/functional/tests/image/v2/test_image.py b/functional/tests/image/v2/test_image.py
index 6a33ad8..809451b 100644
--- a/functional/tests/image/v2/test_image.py
+++ b/functional/tests/image/v2/test_image.py
@@ -27,7 +27,7 @@ class ImageTests(test.TestCase):
@classmethod
def setUpClass(cls):
os.environ['OS_IMAGE_API_VERSION'] = '2'
- opts = cls.get_show_opts(cls.FIELDS)
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack('image create ' + cls.NAME + opts)
expected = cls.NAME + '\n'
cls.assertOutput(expected, raw_output)
@@ -43,17 +43,17 @@ class ImageTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_image_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('image list' + opts)
self.assertIn(self.NAME, raw_output)
def test_image_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('image show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
def test_image_set(self):
- opts = self.get_show_opts([
+ opts = self.get_opts([
"disk_format", "visibility", "min_disk", "min_ram", "name"])
self.openstack('image set --min-disk 4 --min-ram 5 ' +
'--public ' + self.NAME)
@@ -61,13 +61,13 @@ class ImageTests(test.TestCase):
self.assertEqual("raw\n4\n5\n" + self.NAME + '\npublic\n', raw_output)
def test_image_metadata(self):
- opts = self.get_show_opts(["name", "properties"])
+ opts = self.get_opts(["name", "properties"])
self.openstack('image set --property a=b --property c=d ' + self.NAME)
raw_output = self.openstack('image show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output)
def test_image_unset(self):
- opts = self.get_show_opts(["name", "tags", "properties"])
+ opts = self.get_opts(["name", "tags", "properties"])
self.openstack('image set --tag 01 ' + self.NAME)
self.openstack('image unset --tag 01 ' + self.NAME)
# test_image_metadata has set image properties "a" and "c"
diff --git a/functional/tests/network/v2/test_address_scope.py b/functional/tests/network/v2/test_address_scope.py
index 8e25e46..3beab23 100644
--- a/functional/tests/network/v2/test_address_scope.py
+++ b/functional/tests/network/v2/test_address_scope.py
@@ -23,7 +23,7 @@ class AddressScopeTests(test.TestCase):
@classmethod
def setUpClass(cls):
- opts = cls.get_show_opts(cls.FIELDS)
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack('address scope create ' + cls.NAME + opts)
cls.assertOutput(cls.NAME + "\n", raw_output)
@@ -33,17 +33,17 @@ class AddressScopeTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_address_scope_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('address scope list' + opts)
self.assertIn(self.NAME, raw_output)
def test_address_scope_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('address scope show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
def test_address_scope_set(self):
self.openstack('address scope set --share ' + self.NAME)
- opts = self.get_show_opts(['shared'])
+ opts = self.get_opts(['shared'])
raw_output = self.openstack('address scope show ' + self.NAME + opts)
self.assertEqual("True\n", raw_output)
diff --git a/functional/tests/network/v2/test_floating_ip.py b/functional/tests/network/v2/test_floating_ip.py
index f9ecd92..7e7c63f4 100644
--- a/functional/tests/network/v2/test_floating_ip.py
+++ b/functional/tests/network/v2/test_floating_ip.py
@@ -33,14 +33,14 @@ class FloatingIpTests(test.TestCase):
' --subnet-range 10.10.10.0/24 ' +
cls.SUBNET_NAME
)
- opts = cls.get_show_opts(cls.FIELDS)
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack(
- 'ip floating create ' + cls.NETWORK_NAME + opts)
+ 'floating ip create ' + cls.NETWORK_NAME + opts)
cls.ID = raw_output.strip('\n')
@classmethod
def tearDownClass(cls):
- raw_output = cls.openstack('ip floating delete ' + cls.ID)
+ raw_output = cls.openstack('floating ip delete ' + cls.ID)
cls.assertOutput('', raw_output)
raw_output = cls.openstack('subnet delete ' + cls.SUBNET_NAME)
cls.assertOutput('', raw_output)
@@ -48,11 +48,11 @@ class FloatingIpTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_floating_ip_list(self):
- opts = self.get_list_opts(self.HEADERS)
- raw_output = self.openstack('ip floating list' + opts)
+ opts = self.get_opts(self.HEADERS)
+ raw_output = self.openstack('floating ip list' + opts)
self.assertIn(self.ID, raw_output)
def test_floating_ip_show(self):
- opts = self.get_show_opts(self.FIELDS)
- raw_output = self.openstack('ip floating show ' + self.ID + opts)
+ opts = self.get_opts(self.FIELDS)
+ raw_output = self.openstack('floating ip show ' + self.ID + opts)
self.assertEqual(self.ID + "\n", raw_output)
diff --git a/functional/tests/network/v2/test_ip_availability.py b/functional/tests/network/v2/test_ip_availability.py
index f1302d5..e83010f 100644
--- a/functional/tests/network/v2/test_ip_availability.py
+++ b/functional/tests/network/v2/test_ip_availability.py
@@ -25,7 +25,7 @@ class IPAvailabilityTests(test.TestCase):
def setUpClass(cls):
# Create a network for the subnet.
cls.openstack('network create ' + cls.NETWORK_NAME)
- opts = cls.get_show_opts(['name'])
+ opts = cls.get_opts(['name'])
raw_output = cls.openstack(
'subnet create --network ' + cls.NETWORK_NAME +
' --subnet-range 10.10.10.0/24 ' +
@@ -47,7 +47,7 @@ class IPAvailabilityTests(test.TestCase):
self.assertIn(self.NETWORK_NAME, raw_output)
def test_ip_availability_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack(
'ip availability show ' + self.NETWORK_NAME + opts)
self.assertEqual(self.NETWORK_NAME + "\n", raw_output)
diff --git a/functional/tests/network/v2/test_network.py b/functional/tests/network/v2/test_network.py
index 5cc461b..f5c92fa 100644
--- a/functional/tests/network/v2/test_network.py
+++ b/functional/tests/network/v2/test_network.py
@@ -23,7 +23,7 @@ class NetworkTests(test.TestCase):
@classmethod
def setUpClass(cls):
- opts = cls.get_show_opts(cls.FIELDS)
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack('network create ' + cls.NAME + opts)
expected = cls.NAME + '\n'
cls.assertOutput(expected, raw_output)
@@ -34,17 +34,17 @@ class NetworkTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_network_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('network list' + opts)
self.assertIn(self.NAME, raw_output)
def test_network_set(self):
raw_output = self.openstack('network set --disable ' + self.NAME)
- opts = self.get_show_opts(['name', 'admin_state_up'])
+ opts = self.get_opts(['name', 'admin_state_up'])
raw_output = self.openstack('network show ' + self.NAME + opts)
self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output)
def test_network_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('network show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
diff --git a/functional/tests/network/v2/test_network_agent.py b/functional/tests/network/v2/test_network_agent.py
new file mode 100644
index 0000000..f574c50
--- /dev/null
+++ b/functional/tests/network/v2/test_network_agent.py
@@ -0,0 +1,41 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from functional.common import test
+
+
+class NetworkAgentTests(test.TestCase):
+ """Functional tests for network agent. """
+ IDs = None
+ HEADERS = ['ID']
+ FIELDS = ['id']
+
+ @classmethod
+ def test_network_agent_list(cls):
+ opts = cls.get_opts(cls.HEADERS)
+ raw_output = cls.openstack('network agent list' + opts)
+ # get the list of network agent IDs.
+ cls.IDs = raw_output.split('\n')
+
+ def test_network_agent_show(self):
+ opts = self.get_opts(self.FIELDS)
+ raw_output = self.openstack('network agent show ' + self.IDs[0] + opts)
+ self.assertEqual(self.IDs[0] + "\n", raw_output)
+
+ def test_network_agent_set(self):
+ opts = self.get_opts(['admin_state_up'])
+ self.openstack('network agent set --disable ' + self.IDs[0])
+ raw_output = self.openstack('network agent show ' + self.IDs[0] + opts)
+ self.assertEqual("DOWN\n", raw_output)
+ self.openstack('network agent set --enable ' + self.IDs[0])
+ raw_output = self.openstack('network agent show ' + self.IDs[0] + opts)
+ self.assertEqual("UP\n", raw_output)
diff --git a/functional/tests/network/v2/test_network_rbac.py b/functional/tests/network/v2/test_network_rbac.py
new file mode 100644
index 0000000..1dbc246
--- /dev/null
+++ b/functional/tests/network/v2/test_network_rbac.py
@@ -0,0 +1,69 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import uuid
+
+from functional.common import test
+
+
+class NetworkRBACTests(test.TestCase):
+ """Functional tests for network rbac. """
+ NET_NAME = uuid.uuid4().hex
+ PROJECT_NAME = uuid.uuid4().hex
+ OBJECT_ID = None
+ ID = None
+ HEADERS = ['ID']
+ FIELDS = ['id']
+
+ @classmethod
+ def setUpClass(cls):
+ opts = cls.get_opts(cls.FIELDS)
+ raw_output = cls.openstack('network create ' + cls.NET_NAME + opts)
+ cls.OBJECT_ID = raw_output.strip('\n')
+ opts = cls.get_opts(['id', 'object_id'])
+ raw_output = cls.openstack('network rbac create ' +
+ cls.OBJECT_ID +
+ ' --action access_as_shared' +
+ ' --target-project admin' +
+ ' --type network' + opts)
+ cls.ID, object_id, rol = tuple(raw_output.split('\n'))
+ cls.assertOutput(cls.OBJECT_ID, object_id)
+
+ @classmethod
+ def tearDownClass(cls):
+ raw_output_rbac = cls.openstack('network rbac delete ' + cls.ID)
+ raw_output_network = cls.openstack('network delete ' + cls.OBJECT_ID)
+ cls.assertOutput('', raw_output_rbac)
+ cls.assertOutput('', raw_output_network)
+
+ def test_network_rbac_list(self):
+ opts = self.get_opts(self.HEADERS)
+ raw_output = self.openstack('network rbac list' + opts)
+ self.assertIn(self.ID, raw_output)
+
+ def test_network_rbac_show(self):
+ opts = self.get_opts(self.FIELDS)
+ raw_output = self.openstack('network rbac show ' + self.ID + opts)
+ self.assertEqual(self.ID + "\n", raw_output)
+
+ def test_network_rbac_set(self):
+ opts = self.get_opts(self.FIELDS)
+ project_id = self.openstack(
+ 'project create ' + self.PROJECT_NAME + opts)
+ self.openstack('network rbac set ' + self.ID +
+ ' --target-project ' + self.PROJECT_NAME)
+ opts = self.get_opts(['target_project_id'])
+ raw_output_rbac = self.openstack('network rbac show ' + self.ID + opts)
+ raw_output_project = self.openstack(
+ 'project delete ' + self.PROJECT_NAME)
+ self.assertEqual(project_id, raw_output_rbac)
+ self.assertOutput('', raw_output_project)
diff --git a/functional/tests/network/v2/test_network_segment.py b/functional/tests/network/v2/test_network_segment.py
index b5b5dcd..4609973 100644
--- a/functional/tests/network/v2/test_network_segment.py
+++ b/functional/tests/network/v2/test_network_segment.py
@@ -28,12 +28,12 @@ class NetworkSegmentTests(test.TestCase):
@classmethod
def setUpClass(cls):
# Create a network for the segment.
- opts = cls.get_show_opts(['id'])
+ opts = cls.get_opts(['id'])
raw_output = cls.openstack('network create ' + cls.NETWORK_NAME + opts)
cls.NETWORK_ID = raw_output.strip('\n')
# Get the segment for the network.
- opts = cls.get_show_opts(['ID', 'Network'])
+ opts = cls.get_opts(['ID', 'Network'])
raw_output = cls.openstack('--os-beta-command '
'network segment list '
' --network ' + cls.NETWORK_NAME +
@@ -47,13 +47,13 @@ class NetworkSegmentTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_network_segment_list(self):
- opts = self.get_list_opts(['ID'])
+ opts = self.get_opts(['ID'])
raw_output = self.openstack('--os-beta-command '
'network segment list' + opts)
self.assertIn(self.NETWORK_SEGMENT_ID, raw_output)
def test_network_segment_show(self):
- opts = self.get_show_opts(['network_id'])
+ opts = self.get_opts(['network_id'])
raw_output = self.openstack('--os-beta-command '
'network segment show ' +
self.NETWORK_SEGMENT_ID + opts)
diff --git a/functional/tests/network/v2/test_port.py b/functional/tests/network/v2/test_port.py
index 5b358a3..a68019c 100644
--- a/functional/tests/network/v2/test_port.py
+++ b/functional/tests/network/v2/test_port.py
@@ -26,7 +26,7 @@ class PortTests(test.TestCase):
def setUpClass(cls):
# Create a network for the subnet.
cls.openstack('network create ' + cls.NETWORK_NAME)
- opts = cls.get_show_opts(cls.FIELDS)
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack(
'port create --network ' + cls.NETWORK_NAME + ' ' +
cls.NAME + opts
@@ -42,17 +42,17 @@ class PortTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_port_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('port list' + opts)
self.assertIn(self.NAME, raw_output)
def test_port_set(self):
self.openstack('port set --disable ' + self.NAME)
- opts = self.get_show_opts(['name', 'admin_state_up'])
+ opts = self.get_opts(['name', 'admin_state_up'])
raw_output = self.openstack('port show ' + self.NAME + opts)
self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output)
def test_port_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('port show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
diff --git a/functional/tests/network/v2/test_router.py b/functional/tests/network/v2/test_router.py
index ceb7625..e536c64 100644
--- a/functional/tests/network/v2/test_router.py
+++ b/functional/tests/network/v2/test_router.py
@@ -23,7 +23,7 @@ class RouterTests(test.TestCase):
@classmethod
def setUpClass(cls):
- opts = cls.get_show_opts(cls.FIELDS)
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack('router create ' + cls.NAME + opts)
expected = cls.NAME + '\n'
cls.assertOutput(expected, raw_output)
@@ -34,17 +34,17 @@ class RouterTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_router_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('router list' + opts)
self.assertIn(self.NAME, raw_output)
def test_router_set(self):
self.openstack('router set --disable ' + self.NAME)
- opts = self.get_show_opts(['name', 'admin_state_up'])
+ opts = self.get_opts(['name', 'admin_state_up'])
raw_output = self.openstack('router show ' + self.NAME + opts)
self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output)
def test_router_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('router show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
diff --git a/functional/tests/network/v2/test_security_group.py b/functional/tests/network/v2/test_security_group.py
index 4fc4d12..2a3b92a 100644
--- a/functional/tests/network/v2/test_security_group.py
+++ b/functional/tests/network/v2/test_security_group.py
@@ -24,7 +24,7 @@ class SecurityGroupTests(test.TestCase):
@classmethod
def setUpClass(cls):
- opts = cls.get_show_opts(cls.FIELDS)
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack('security group create ' + cls.NAME + opts)
expected = cls.NAME + '\n'
cls.assertOutput(expected, raw_output)
@@ -40,7 +40,7 @@ class SecurityGroupTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_security_group_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('security group list' + opts)
self.assertIn(self.NAME, raw_output)
@@ -50,11 +50,11 @@ class SecurityGroupTests(test.TestCase):
)
self.assertEqual('', raw_output)
- opts = self.get_show_opts(['description'])
+ opts = self.get_opts(['description'])
raw_output = self.openstack('security group show ' + self.NAME + opts)
self.assertEqual("NSA\n", raw_output)
def test_security_group_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('security group show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
diff --git a/functional/tests/network/v2/test_security_group_rule.py b/functional/tests/network/v2/test_security_group_rule.py
index 64e1fcd..248d20b 100644
--- a/functional/tests/network/v2/test_security_group_rule.py
+++ b/functional/tests/network/v2/test_security_group_rule.py
@@ -26,7 +26,7 @@ class SecurityGroupRuleTests(test.TestCase):
@classmethod
def setUpClass(cls):
# Create the security group to hold the rule.
- opts = cls.get_show_opts(cls.NAME_FIELD)
+ opts = cls.get_opts(cls.NAME_FIELD)
raw_output = cls.openstack('security group create ' +
cls.SECURITY_GROUP_NAME +
opts)
@@ -34,7 +34,7 @@ class SecurityGroupRuleTests(test.TestCase):
cls.assertOutput(expected, raw_output)
# Create the security group rule.
- opts = cls.get_show_opts(cls.ID_FIELD)
+ opts = cls.get_opts(cls.ID_FIELD)
raw_output = cls.openstack('security group rule create ' +
cls.SECURITY_GROUP_NAME +
' --protocol tcp --dst-port 80:80' +
@@ -53,14 +53,14 @@ class SecurityGroupRuleTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_security_group_rule_list(self):
- opts = self.get_list_opts(self.ID_HEADER)
+ opts = self.get_opts(self.ID_HEADER)
raw_output = self.openstack('security group rule list ' +
self.SECURITY_GROUP_NAME +
opts)
self.assertIn(self.SECURITY_GROUP_RULE_ID, raw_output)
def test_security_group_rule_show(self):
- opts = self.get_show_opts(self.ID_FIELD)
+ opts = self.get_opts(self.ID_FIELD)
raw_output = self.openstack('security group rule show ' +
self.SECURITY_GROUP_RULE_ID +
opts)
diff --git a/functional/tests/network/v2/test_subnet.py b/functional/tests/network/v2/test_subnet.py
index 7697e0f..11ae6da 100644
--- a/functional/tests/network/v2/test_subnet.py
+++ b/functional/tests/network/v2/test_subnet.py
@@ -26,7 +26,7 @@ class SubnetTests(test.TestCase):
def setUpClass(cls):
# Create a network for the subnet.
cls.openstack('network create ' + cls.NETWORK_NAME)
- opts = cls.get_show_opts(cls.FIELDS)
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack(
'subnet create --network ' + cls.NETWORK_NAME +
' --subnet-range 10.10.10.0/24 ' +
@@ -43,17 +43,17 @@ class SubnetTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_subnet_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('subnet list' + opts)
self.assertIn(self.NAME, raw_output)
def test_subnet_set(self):
self.openstack('subnet set --no-dhcp ' + self.NAME)
- opts = self.get_show_opts(['name', 'enable_dhcp'])
+ opts = self.get_opts(['name', 'enable_dhcp'])
raw_output = self.openstack('subnet show ' + self.NAME + opts)
self.assertEqual("False\n" + self.NAME + "\n", raw_output)
def test_subnet_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('subnet show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
diff --git a/functional/tests/network/v2/test_subnet_pool.py b/functional/tests/network/v2/test_subnet_pool.py
index 1515487..054188f 100644
--- a/functional/tests/network/v2/test_subnet_pool.py
+++ b/functional/tests/network/v2/test_subnet_pool.py
@@ -25,7 +25,7 @@ class SubnetPoolTests(test.TestCase):
@classmethod
def setUpClass(cls):
- opts = cls.get_show_opts(cls.FIELDS)
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack('subnet pool create --pool-prefix ' +
cls.CREATE_POOL_PREFIX + ' ' +
cls.NAME + opts)
@@ -37,19 +37,19 @@ class SubnetPoolTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_subnet_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('subnet pool list' + opts)
self.assertIn(self.NAME, raw_output)
def test_subnet_set(self):
self.openstack('subnet pool set --pool-prefix ' +
self.SET_POOL_PREFIX + ' ' + self.NAME)
- opts = self.get_show_opts(['prefixes', 'name'])
+ opts = self.get_opts(['prefixes', 'name'])
raw_output = self.openstack('subnet pool show ' + self.NAME + opts)
self.assertEqual(self.NAME + '\n' + self.SET_POOL_PREFIX + '\n',
raw_output)
def test_subnet_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('subnet pool show ' + self.NAME + opts)
self.assertEqual(self.NAME + '\n', raw_output)
diff --git a/functional/tests/object/v1/test_container.py b/functional/tests/object/v1/test_container.py
index 8721a4a..4f9e843 100644
--- a/functional/tests/object/v1/test_container.py
+++ b/functional/tests/object/v1/test_container.py
@@ -21,10 +21,9 @@ class ContainerTests(test.TestCase):
@classmethod
def setUpClass(cls):
- opts = cls.get_list_opts(['container'])
+ opts = cls.get_opts(['container'])
raw_output = cls.openstack('container create ' + cls.NAME + opts)
- expected = '"container"\n"' + cls.NAME + '"\n'
- cls.assertOutput(expected, raw_output)
+ cls.assertOutput(cls.NAME + '\n', raw_output)
@classmethod
def tearDownClass(cls):
@@ -32,11 +31,11 @@ class ContainerTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_container_list(self):
- opts = self.get_list_opts(['Name'])
+ opts = self.get_opts(['Name'])
raw_output = self.openstack('container list' + opts)
self.assertIn(self.NAME, raw_output)
def test_container_show(self):
- opts = self.get_show_opts(['container'])
+ opts = self.get_opts(['container'])
raw_output = self.openstack('container show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
diff --git a/functional/tests/volume/v1/test_qos.py b/functional/tests/volume/v1/test_qos.py
index f4b2fec..5aed4bd 100644
--- a/functional/tests/volume/v1/test_qos.py
+++ b/functional/tests/volume/v1/test_qos.py
@@ -15,7 +15,7 @@ import uuid
from functional.tests.volume.v1 import common
-class VolumeTests(common.BaseVolumeTests):
+class QosTests(common.BaseVolumeTests):
"""Functional tests for volume qos. """
NAME = uuid.uuid4().hex
@@ -25,7 +25,8 @@ class VolumeTests(common.BaseVolumeTests):
@classmethod
def setUpClass(cls):
- opts = cls.get_show_opts(cls.FIELDS)
+ super(QosTests, cls).setUpClass()
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack('volume qos create ' + cls.NAME + opts)
cls.ID, name, rol = raw_output.split('\n')
cls.assertOutput(cls.NAME, name)
@@ -36,12 +37,12 @@ class VolumeTests(common.BaseVolumeTests):
cls.assertOutput('', raw_output)
def test_volume_qos_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('volume qos list' + opts)
self.assertIn(self.NAME, raw_output)
def test_volume_qos_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('volume qos show ' + self.ID + opts)
self.assertEqual(self.ID + "\n" + self.NAME + "\n", raw_output)
@@ -49,6 +50,6 @@ class VolumeTests(common.BaseVolumeTests):
raw_output = self.openstack(
'volume qos set --property a=b --property c=d ' + self.ID)
self.assertEqual("", raw_output)
- opts = self.get_show_opts(['name', 'specs'])
+ opts = self.get_opts(['name', 'specs'])
raw_output = self.openstack('volume qos show ' + self.ID + opts)
self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output)
diff --git a/functional/tests/volume/v1/test_volume.py b/functional/tests/volume/v1/test_volume.py
index 874be6e..8275bf0 100644
--- a/functional/tests/volume/v1/test_volume.py
+++ b/functional/tests/volume/v1/test_volume.py
@@ -10,7 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import os
import uuid
from functional.tests.volume.v1 import common
@@ -26,8 +25,8 @@ class VolumeTests(common.BaseVolumeTests):
@classmethod
def setUpClass(cls):
- os.environ['OS_VOLUME_API_VERSION'] = '1'
- opts = cls.get_show_opts(cls.FIELDS)
+ super(VolumeTests, cls).setUpClass()
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts)
expected = cls.NAME + '\n'
cls.assertOutput(expected, raw_output)
@@ -43,12 +42,12 @@ class VolumeTests(common.BaseVolumeTests):
cls.assertOutput('', raw_output)
def test_volume_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('volume list' + opts)
self.assertIn(self.NAME, raw_output)
def test_volume_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('volume show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
@@ -56,7 +55,7 @@ class VolumeTests(common.BaseVolumeTests):
raw_output = self.openstack(
'volume set --property a=b --property c=d ' + self.NAME)
self.assertEqual("", raw_output)
- opts = self.get_show_opts(["properties"])
+ opts = self.get_opts(["properties"])
raw_output = self.openstack('volume show ' + self.NAME + opts)
self.assertEqual("a='b', c='d'\n", raw_output)
@@ -67,12 +66,12 @@ class VolumeTests(common.BaseVolumeTests):
def test_volume_set(self):
self.openstack('volume set --description RAMAC ' + self.NAME)
- opts = self.get_show_opts(["display_description", "display_name"])
+ opts = self.get_opts(["display_description", "display_name"])
raw_output = self.openstack('volume show ' + self.NAME + opts)
self.assertEqual("RAMAC\n" + self.NAME + "\n", raw_output)
def test_volume_set_size(self):
self.openstack('volume set --size 2 ' + self.NAME)
- opts = self.get_show_opts(["display_name", "size"])
+ opts = self.get_opts(["display_name", "size"])
raw_output = self.openstack('volume show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n2\n", raw_output)
diff --git a/functional/tests/volume/v1/test_volume_type.py b/functional/tests/volume/v1/test_volume_type.py
index dc8b1f4..ed44f3c 100644
--- a/functional/tests/volume/v1/test_volume_type.py
+++ b/functional/tests/volume/v1/test_volume_type.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
import uuid
from functional.tests.volume.v1 import common
@@ -24,7 +25,8 @@ class VolumeTypeTests(common.BaseVolumeTests):
@classmethod
def setUpClass(cls):
- opts = cls.get_show_opts(cls.FIELDS)
+ super(VolumeTypeTests, cls).setUpClass()
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack('volume type create ' + cls.NAME + opts)
expected = cls.NAME + '\n'
cls.assertOutput(expected, raw_output)
@@ -35,12 +37,12 @@ class VolumeTypeTests(common.BaseVolumeTests):
cls.assertOutput('', raw_output)
def test_volume_type_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('volume type list' + opts)
self.assertIn(self.NAME, raw_output)
def test_volume_type_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('volume type show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
@@ -49,7 +51,7 @@ class VolumeTypeTests(common.BaseVolumeTests):
'volume type set --property a=b --property c=d ' + self.NAME)
self.assertEqual("", raw_output)
- opts = self.get_show_opts(["properties"])
+ opts = self.get_opts(["properties"])
raw_output = self.openstack('volume type show ' + self.NAME + opts)
self.assertEqual("a='b', c='d'\n", raw_output)
@@ -58,3 +60,15 @@ class VolumeTypeTests(common.BaseVolumeTests):
self.assertEqual("", raw_output)
raw_output = self.openstack('volume type show ' + self.NAME + opts)
self.assertEqual("c='d'\n", raw_output)
+
+ def test_multi_delete(self):
+ vol_type1 = uuid.uuid4().hex
+ vol_type2 = uuid.uuid4().hex
+ self.openstack('volume type create ' + vol_type1)
+ time.sleep(5)
+ self.openstack('volume type create ' + vol_type2)
+ time.sleep(5)
+ cmd = 'volume type delete %s %s' % (vol_type1, vol_type2)
+ time.sleep(5)
+ raw_output = self.openstack(cmd)
+ self.assertOutput('', raw_output)
diff --git a/functional/tests/volume/v2/common.py b/functional/tests/volume/v2/common.py
new file mode 100644
index 0000000..8652c2d
--- /dev/null
+++ b/functional/tests/volume/v2/common.py
@@ -0,0 +1,23 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+
+from functional.common import test
+
+
+class BaseVolumeTests(test.TestCase):
+ """Base class for Volume functional tests. """
+
+ @classmethod
+ def setUpClass(cls):
+ os.environ['OS_VOLUME_API_VERSION'] = '2'
diff --git a/functional/tests/volume/v2/test_qos.py b/functional/tests/volume/v2/test_qos.py
index 24ce1b3..5311b47 100644
--- a/functional/tests/volume/v2/test_qos.py
+++ b/functional/tests/volume/v2/test_qos.py
@@ -12,10 +12,10 @@
import uuid
-from functional.common import test
+from functional.tests.volume.v2 import common
-class VolumeTests(test.TestCase):
+class QosTests(common.BaseVolumeTests):
"""Functional tests for volume qos. """
NAME = uuid.uuid4().hex
@@ -25,7 +25,8 @@ class VolumeTests(test.TestCase):
@classmethod
def setUpClass(cls):
- opts = cls.get_show_opts(cls.FIELDS)
+ super(QosTests, cls).setUpClass()
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack('volume qos create ' + cls.NAME + opts)
cls.ID, name, rol = raw_output.split('\n')
cls.assertOutput(cls.NAME, name)
@@ -36,12 +37,12 @@ class VolumeTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_volume_qos_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('volume qos list' + opts)
self.assertIn(self.NAME, raw_output)
def test_volume_qos_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('volume qos show ' + self.ID + opts)
self.assertEqual(self.ID + "\n" + self.NAME + "\n", raw_output)
@@ -49,13 +50,13 @@ class VolumeTests(test.TestCase):
raw_output = self.openstack(
'volume qos set --property a=b --property c=d ' + self.ID)
self.assertEqual("", raw_output)
- opts = self.get_show_opts(['name', 'specs'])
+ opts = self.get_opts(['name', 'specs'])
raw_output = self.openstack('volume qos show ' + self.ID + opts)
self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output)
raw_output = self.openstack(
'volume qos unset --property a ' + self.ID)
self.assertEqual("", raw_output)
- opts = self.get_show_opts(['name', 'specs'])
+ opts = self.get_opts(['name', 'specs'])
raw_output = self.openstack('volume qos show ' + self.ID + opts)
self.assertEqual(self.NAME + "\nc='d'\n", raw_output)
diff --git a/functional/tests/volume/v1/test_snapshot.py b/functional/tests/volume/v2/test_snapshot.py
index c43a745..4f91083 100644
--- a/functional/tests/volume/v1/test_snapshot.py
+++ b/functional/tests/volume/v2/test_snapshot.py
@@ -13,10 +13,10 @@
import time
import uuid
-from functional.common import test
+from functional.tests.volume.v2 import common
-class SnapshotTests(test.TestCase):
+class SnapshotTests(common.BaseVolumeTests):
"""Functional tests for snapshot. """
VOLLY = uuid.uuid4().hex
@@ -26,7 +26,7 @@ class SnapshotTests(test.TestCase):
@classmethod
def wait_for_status(cls, command, status, tries):
- opts = cls.get_show_opts(['status'])
+ opts = cls.get_opts(['status'])
for attempt in range(tries):
time.sleep(1)
raw_output = cls.openstack(command + opts)
@@ -36,9 +36,10 @@ class SnapshotTests(test.TestCase):
@classmethod
def setUpClass(cls):
+ super(SnapshotTests, cls).setUpClass()
cls.openstack('volume create --size 1 ' + cls.VOLLY)
cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 3)
- opts = cls.get_show_opts(['status'])
+ opts = cls.get_opts(['status'])
raw_output = cls.openstack('snapshot create --name ' + cls.NAME +
' ' + cls.VOLLY + opts)
cls.assertOutput('creating\n', raw_output)
@@ -56,7 +57,7 @@ class SnapshotTests(test.TestCase):
cls.openstack('volume delete --force ' + cls.VOLLY, fail_ok=True)
def test_snapshot_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('snapshot list' + opts)
self.assertIn(self.NAME, raw_output)
@@ -64,7 +65,7 @@ class SnapshotTests(test.TestCase):
raw_output = self.openstack(
'snapshot set --property a=b --property c=d ' + self.NAME)
self.assertEqual("", raw_output)
- opts = self.get_show_opts(["properties"])
+ opts = self.get_opts(["properties"])
raw_output = self.openstack('snapshot show ' + self.NAME + opts)
self.assertEqual("a='b', c='d'\n", raw_output)
@@ -77,6 +78,6 @@ class SnapshotTests(test.TestCase):
raw_output = self.openstack(
'snapshot set --description backup ' + self.NAME)
self.assertEqual("", raw_output)
- opts = self.get_show_opts(["description", "name"])
+ opts = self.get_opts(["description", "name"])
raw_output = self.openstack('snapshot show ' + self.NAME + opts)
self.assertEqual("backup\n" + self.NAME + "\n", raw_output)
diff --git a/functional/tests/volume/v2/test_volume.py b/functional/tests/volume/v2/test_volume.py
index 9c7f11a..02324a1 100644
--- a/functional/tests/volume/v2/test_volume.py
+++ b/functional/tests/volume/v2/test_volume.py
@@ -10,14 +10,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-import os
import time
import uuid
-from functional.common import test
+from functional.tests.volume.v2 import common
-class VolumeTests(test.TestCase):
+class VolumeTests(common.BaseVolumeTests):
"""Functional tests for volume. """
NAME = uuid.uuid4().hex
@@ -29,8 +28,8 @@ class VolumeTests(test.TestCase):
@classmethod
def setUpClass(cls):
- os.environ['OS_VOLUME_API_VERSION'] = '2'
- opts = cls.get_show_opts(cls.FIELDS)
+ super(VolumeTests, cls).setUpClass()
+ opts = cls.get_opts(cls.FIELDS)
# Create test volume
raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts)
@@ -44,17 +43,24 @@ class VolumeTests(test.TestCase):
'volume set --name ' + cls.OTHER_NAME + ' ' + cls.NAME)
cls.assertOutput('', raw_output)
+ # Set volume state
+ cls.openstack('volume set --state error ' + cls.OTHER_NAME)
+ opts = cls.get_opts(["status"])
+ raw_output_status = cls.openstack(
+ 'volume show ' + cls.OTHER_NAME + opts)
+
# Delete test volume
raw_output = cls.openstack('volume delete ' + cls.OTHER_NAME)
cls.assertOutput('', raw_output)
+ cls.assertOutput('error\n', raw_output_status)
def test_volume_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('volume list' + opts)
self.assertIn(self.NAME, raw_output)
def test_volume_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('volume show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
@@ -62,7 +68,7 @@ class VolumeTests(test.TestCase):
raw_output = self.openstack(
'volume set --property a=b --property c=d ' + self.NAME)
self.assertEqual("", raw_output)
- opts = self.get_show_opts(["properties"])
+ opts = self.get_opts(["properties"])
raw_output = self.openstack('volume show ' + self.NAME + opts)
self.assertEqual("a='b', c='d'\n", raw_output)
@@ -75,18 +81,18 @@ class VolumeTests(test.TestCase):
discription = uuid.uuid4().hex
self.openstack('volume set --description ' + discription + ' ' +
self.NAME)
- opts = self.get_show_opts(["description", "name"])
+ opts = self.get_opts(["description", "name"])
raw_output = self.openstack('volume show ' + self.NAME + opts)
self.assertEqual(discription + "\n" + self.NAME + "\n", raw_output)
def test_volume_set_size(self):
self.openstack('volume set --size 2 ' + self.NAME)
- opts = self.get_show_opts(["name", "size"])
+ opts = self.get_opts(["name", "size"])
raw_output = self.openstack('volume show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n2\n", raw_output)
def test_volume_snapshot(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
# Create snapshot from test volume
raw_output = self.openstack('snapshot create ' + self.NAME +
@@ -111,12 +117,13 @@ class VolumeTests(test.TestCase):
# Delete test snapshot
raw_output = self.openstack('snapshot delete ' + self.SNAPSHOT_NAME)
self.assertOutput('', raw_output)
+ self.wait_for("volume", self.NAME, "available")
def wait_for(self, check_type, check_name, desired_status, wait=120,
interval=5, failures=['ERROR']):
status = "notset"
total_sleep = 0
- opts = self.get_show_opts(['status'])
+ opts = self.get_opts(['status'])
while total_sleep < wait:
status = self.openstack(check_type + ' show ' + check_name + opts)
status = status.rstrip()
diff --git a/functional/tests/volume/v2/test_volume_type.py b/functional/tests/volume/v2/test_volume_type.py
index d8a7a7e..02f790e 100644
--- a/functional/tests/volume/v2/test_volume_type.py
+++ b/functional/tests/volume/v2/test_volume_type.py
@@ -10,12 +10,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
import uuid
-from functional.common import test
+from functional.tests.volume.v2 import common
-class VolumeTypeTests(test.TestCase):
+class VolumeTypeTests(common.BaseVolumeTests):
"""Functional tests for volume type. """
NAME = uuid.uuid4().hex
@@ -24,7 +25,8 @@ class VolumeTypeTests(test.TestCase):
@classmethod
def setUpClass(cls):
- opts = cls.get_show_opts(cls.FIELDS)
+ super(VolumeTypeTests, cls).setUpClass()
+ opts = cls.get_opts(cls.FIELDS)
raw_output = cls.openstack(
'volume type create --private ' + cls.NAME + opts)
expected = cls.NAME + '\n'
@@ -36,12 +38,12 @@ class VolumeTypeTests(test.TestCase):
cls.assertOutput('', raw_output)
def test_volume_type_list(self):
- opts = self.get_list_opts(self.HEADERS)
+ opts = self.get_opts(self.HEADERS)
raw_output = self.openstack('volume type list' + opts)
self.assertIn(self.NAME, raw_output)
def test_volume_type_show(self):
- opts = self.get_show_opts(self.FIELDS)
+ opts = self.get_opts(self.FIELDS)
raw_output = self.openstack('volume type show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\n", raw_output)
@@ -50,7 +52,7 @@ class VolumeTypeTests(test.TestCase):
'volume type set --property a=b --property c=d ' + self.NAME)
self.assertEqual("", raw_output)
- opts = self.get_show_opts(["properties"])
+ opts = self.get_opts(["properties"])
raw_output = self.openstack('volume type show ' + self.NAME + opts)
self.assertEqual("a='b', c='d'\n", raw_output)
@@ -68,3 +70,15 @@ class VolumeTypeTests(test.TestCase):
raw_output = self.openstack(
'volume type unset --project admin ' + self.NAME)
self.assertEqual("", raw_output)
+
+ def test_multi_delete(self):
+ vol_type1 = uuid.uuid4().hex
+ vol_type2 = uuid.uuid4().hex
+ self.openstack('volume type create ' + vol_type1)
+ time.sleep(5)
+ self.openstack('volume type create ' + vol_type2)
+ time.sleep(5)
+ cmd = 'volume type delete %s %s' % (vol_type1, vol_type2)
+ time.sleep(5)
+ raw_output = self.openstack(cmd)
+ self.assertOutput('', raw_output)
diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py
index bd2abd8..04d88f3 100644
--- a/openstackclient/api/api.py
+++ b/openstackclient/api/api.py
@@ -17,8 +17,9 @@ import simplejson as json
from keystoneauth1 import exceptions as ks_exceptions
from keystoneauth1 import session as ks_session
+from osc_lib import exceptions
-from openstackclient.common import exceptions
+from openstackclient.i18n import _
class KeystoneSession(object):
@@ -255,9 +256,11 @@ class BaseAPI(KeystoneSession):
if len(data) == 1:
return data[0]
if len(data) > 1:
- msg = "Multiple %s exist with %s='%s'"
+ msg = _("Multiple %(resource)s exist with %(attr)s='%(value)s'")
raise exceptions.CommandError(
- msg % (resource, attr, value),
+ msg % {'resource': resource,
+ 'attr': attr,
+ 'value': value}
)
# Search by id
@@ -265,8 +268,12 @@ class BaseAPI(KeystoneSession):
data = getlist(kwargs)
if len(data) == 1:
return data[0]
- msg = "No %s with a %s or ID of '%s' found"
- raise exceptions.CommandError(msg % (resource, attr, value))
+ msg = _("No %(resource)s with a %(attr)s or ID of '%(value)s' found")
+ raise exceptions.CommandError(
+ msg % {'resource': resource,
+ 'attr': attr,
+ 'value': value}
+ )
def find_bulk(
self,
@@ -314,10 +321,10 @@ class BaseAPI(KeystoneSession):
bulk_list = self.find_bulk(path, **kwargs)
num_bulk = len(bulk_list)
if num_bulk == 0:
- msg = "none found"
+ msg = _("none found")
raise exceptions.NotFound(msg)
elif num_bulk > 1:
- msg = "many found"
+ msg = _("many found")
raise RuntimeError(msg)
return bulk_list[0]
@@ -344,7 +351,7 @@ class BaseAPI(KeystoneSession):
try:
ret = self.find_one("/%s/detail" % (path), **kwargs)
except ks_exceptions.NotFound:
- msg = "%s not found" % value
+ msg = _("%s not found") % value
raise exceptions.NotFound(msg)
return ret
diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py
index c74e800..d62a82d 100644
--- a/openstackclient/api/auth.py
+++ b/openstackclient/api/auth.py
@@ -11,234 +11,15 @@
# under the License.
#
-"""Authentication Library"""
+# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release
+# or Jun 2017.
-import argparse
-import logging
+import sys
-import stevedore
+from osc_lib.api.auth import * # noqa
-from keystoneclient.auth import base
-from openstackclient.common import exceptions as exc
-from openstackclient.common import utils
-from openstackclient.i18n import _
-
-
-LOG = logging.getLogger(__name__)
-
-# Initialize the list of Authentication plugins early in order
-# to get the command-line options
-PLUGIN_LIST = None
-
-# List of plugin command line options
-OPTIONS_LIST = {}
-
-
-def get_plugin_list():
- """Gather plugin list and cache it"""
-
- global PLUGIN_LIST
-
- if PLUGIN_LIST is None:
- PLUGIN_LIST = stevedore.ExtensionManager(
- base.PLUGIN_NAMESPACE,
- invoke_on_load=False,
- propagate_map_exceptions=True,
- )
- return PLUGIN_LIST
-
-
-def get_options_list():
- """Gather plugin options so the help action has them available"""
-
- global OPTIONS_LIST
-
- if not OPTIONS_LIST:
- for plugin in get_plugin_list():
- for o in plugin.plugin.get_options():
- os_name = o.dest.lower().replace('_', '-')
- os_env_name = 'OS_' + os_name.upper().replace('-', '_')
- OPTIONS_LIST.setdefault(
- os_name, {'env': os_env_name, 'help': ''},
- )
- # TODO(mhu) simplistic approach, would be better to only add
- # help texts if they vary from one auth plugin to another
- # also the text rendering is ugly in the CLI ...
- OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % (
- plugin.name,
- o.help,
- )
- return OPTIONS_LIST
-
-
-def select_auth_plugin(options):
- """Pick an auth plugin based on --os-auth-type or other options"""
-
- auth_plugin_name = None
-
- # Do the token/url check first as this must override the default
- # 'password' set by os-client-config
- # Also, url and token are not copied into o-c-c's auth dict (yet?)
- if options.auth.get('url') and options.auth.get('token'):
- # service token authentication
- auth_plugin_name = 'token_endpoint'
- elif options.auth_type in [plugin.name for plugin in PLUGIN_LIST]:
- # A direct plugin name was given, use it
- auth_plugin_name = options.auth_type
- elif options.auth.get('username'):
- if options.identity_api_version == '3':
- auth_plugin_name = 'v3password'
- elif options.identity_api_version.startswith('2'):
- auth_plugin_name = 'v2password'
- else:
- # let keystoneclient figure it out itself
- auth_plugin_name = 'osc_password'
- elif options.auth.get('token'):
- if options.identity_api_version == '3':
- auth_plugin_name = 'v3token'
- elif options.identity_api_version.startswith('2'):
- auth_plugin_name = 'v2token'
- else:
- # let keystoneclient figure it out itself
- auth_plugin_name = 'token'
- else:
- # The ultimate default is similar to the original behaviour,
- # but this time with version discovery
- auth_plugin_name = 'osc_password'
- LOG.debug("Auth plugin %s selected", auth_plugin_name)
- return auth_plugin_name
-
-
-def build_auth_params(auth_plugin_name, cmd_options):
-
- auth_params = dict(cmd_options.auth)
- if auth_plugin_name:
- LOG.debug('auth_type: %s', auth_plugin_name)
- auth_plugin_class = base.get_plugin_class(auth_plugin_name)
- # grab tenant from project for v2.0 API compatibility
- if auth_plugin_name.startswith("v2"):
- if 'project_id' in auth_params:
- auth_params['tenant_id'] = auth_params['project_id']
- del auth_params['project_id']
- if 'project_name' in auth_params:
- auth_params['tenant_name'] = auth_params['project_name']
- del auth_params['project_name']
- else:
- LOG.debug('no auth_type')
- # delay the plugin choice, grab every option
- auth_plugin_class = None
- plugin_options = set([o.replace('-', '_') for o in get_options_list()])
- for option in plugin_options:
- LOG.debug('fetching option %s', option)
- auth_params[option] = getattr(cmd_options.auth, option, None)
- return (auth_plugin_class, auth_params)
-
-
-def check_valid_auth_options(options, auth_plugin_name, required_scope=True):
- """Perform basic option checking, provide helpful error messages.
-
- :param required_scope: indicate whether a scoped token is required
-
- """
-
- msgs = []
- if auth_plugin_name.endswith('password'):
- if not options.auth.get('username'):
- msgs.append(_('Set a username with --os-username, OS_USERNAME,'
- ' or auth.username'))
- if not options.auth.get('auth_url'):
- msgs.append(_('Set an authentication URL, with --os-auth-url,'
- ' OS_AUTH_URL or auth.auth_url'))
- if (required_scope and not
- options.auth.get('project_id') and not
- options.auth.get('domain_id') and not
- options.auth.get('domain_name') and not
- options.auth.get('project_name') and not
- options.auth.get('tenant_id') and not
- options.auth.get('tenant_name')):
- msgs.append(_('Set a scope, such as a project or domain, set a '
- 'project scope with --os-project-name, '
- 'OS_PROJECT_NAME or auth.project_name, set a domain '
- 'scope with --os-domain-name, OS_DOMAIN_NAME or '
- 'auth.domain_name'))
- elif auth_plugin_name.endswith('token'):
- if not options.auth.get('token'):
- msgs.append(_('Set a token with --os-token, OS_TOKEN or '
- 'auth.token'))
- if not options.auth.get('auth_url'):
- msgs.append(_('Set a service AUTH_URL, with --os-auth-url, '
- 'OS_AUTH_URL or auth.auth_url'))
- elif auth_plugin_name == 'token_endpoint':
- if not options.auth.get('token'):
- msgs.append(_('Set a token with --os-token, OS_TOKEN or '
- 'auth.token'))
- if not options.auth.get('url'):
- msgs.append(_('Set a service URL, with --os-url, OS_URL or '
- 'auth.url'))
-
- if msgs:
- raise exc.CommandError('Missing parameter(s): \n%s' % '\n'.join(msgs))
-
-
-def build_auth_plugins_option_parser(parser):
- """Auth plugins options builder
-
- Builds dynamically the list of options expected by each available
- authentication plugin.
-
- """
- available_plugins = [plugin.name for plugin in get_plugin_list()]
- parser.add_argument(
- '--os-auth-type',
- metavar='<auth-type>',
- dest='auth_type',
- default=utils.env('OS_AUTH_TYPE'),
- help='Select an authentication type. Available types: ' +
- ', '.join(available_plugins) +
- '. Default: selected based on --os-username/--os-token' +
- ' (Env: OS_AUTH_TYPE)',
- choices=available_plugins
- )
- # Maintain compatibility with old tenant env vars
- envs = {
- 'OS_PROJECT_NAME': utils.env(
- 'OS_PROJECT_NAME',
- default=utils.env('OS_TENANT_NAME')
- ),
- 'OS_PROJECT_ID': utils.env(
- 'OS_PROJECT_ID',
- default=utils.env('OS_TENANT_ID')
- ),
- }
- for o in get_options_list():
- # Remove tenant options from KSC plugins and replace them below
- if 'tenant' not in o:
- parser.add_argument(
- '--os-' + o,
- metavar='<auth-%s>' % o,
- dest=o.replace('-', '_'),
- default=envs.get(
- OPTIONS_LIST[o]['env'],
- utils.env(OPTIONS_LIST[o]['env']),
- ),
- help='%s\n(Env: %s)' % (
- OPTIONS_LIST[o]['help'],
- OPTIONS_LIST[o]['env'],
- ),
- )
- # add tenant-related options for compatibility
- # this is deprecated but still used in some tempest tests...
- parser.add_argument(
- '--os-tenant-name',
- metavar='<auth-tenant-name>',
- dest='os_project_name',
- help=argparse.SUPPRESS,
- )
- parser.add_argument(
- '--os-tenant-id',
- metavar='<auth-tenant-id>',
- dest='os_project_id',
- help=argparse.SUPPRESS,
- )
- return parser
+sys.stderr.write(
+ "WARNING: %s is deprecated and will be removed after Jun 2017. "
+ "Please use osc_lib.api.auth\n" % __name__
+)
diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py
index cff0b75..a106b1e 100644
--- a/openstackclient/api/auth_plugin.py
+++ b/openstackclient/api/auth_plugin.py
@@ -13,95 +13,49 @@
"""Authentication Plugin Library"""
-import logging
+from keystoneauth1 import loading
+from keystoneauth1 import token_endpoint
-from oslo_config import cfg
-from six.moves.urllib import parse as urlparse
+from openstackclient.i18n import _
-from keystoneclient.auth.identity.generic import password as ksc_password
-from keystoneclient.auth import token_endpoint
-LOG = logging.getLogger(__name__)
-
-
-class TokenEndpoint(token_endpoint.Token):
+class TokenEndpoint(loading.BaseLoader):
"""Auth plugin to handle traditional token/endpoint usage
- Implements the methods required to handle token authentication
- with a user-specified token and service endpoint; no Identity calls
- are made for re-scoping, service catalog lookups or the like.
-
- The purpose of this plugin is to get rid of the special-case paths
- in the code to handle this authentication format. Its primary use
- is for bootstrapping the Keystone database.
+ Keystoneauth contains a Token plugin class that now correctly
+ handles the token/endpoint auth compatible with OSC. However,
+ the AdminToken loader deprecates the 'url' argument, which breaks
+ OSC compatibility, so make one that works.
"""
- def __init__(self, url, token, **kwargs):
+ @property
+ def plugin_class(self):
+ return token_endpoint.Token
+
+ def load_from_options(self, url, token, **kwargs):
"""A plugin for static authentication with an existing token
:param string url: Service endpoint
:param string token: Existing token
"""
- super(TokenEndpoint, self).__init__(endpoint=url,
- token=token)
- def get_auth_ref(self, session, **kwargs):
- # Stub this method for compatibility
- return None
+ return super(TokenEndpoint, self).load_from_options(
+ endpoint=url,
+ token=token,
+ )
- @classmethod
def get_options(self):
- options = super(TokenEndpoint, self).get_options()
-
- options.extend([
- # Maintain name 'url' for compatibility
- cfg.StrOpt('url',
- help='Specific service endpoint to use'),
- cfg.StrOpt('token',
- secret=True,
- help='Authentication token to use'),
- ])
-
+ """Return the legacy options"""
+
+ options = [
+ loading.Opt(
+ 'url',
+ help=_('Specific service endpoint to use'),
+ ),
+ loading.Opt(
+ 'token',
+ secret=True,
+ help=_('Authentication token to use'),
+ ),
+ ]
return options
-
-
-class OSCGenericPassword(ksc_password.Password):
- """Auth plugin hack to work around broken Keystone configurations
-
- The default Keystone configuration uses http://localhost:xxxx in
- admin_endpoint and public_endpoint and are returned in the links.href
- attribute by the version routes. Deployments that do not set these
- are unusable with newer keystoneclient version discovery.
-
- """
-
- def create_plugin(self, session, version, url, raw_status=None):
- """Handle default Keystone endpoint configuration
-
- Build the actual API endpoint from the scheme, host and port of the
- original auth URL and the rest from the returned version URL.
- """
-
- ver_u = urlparse.urlparse(url)
-
- # Only hack this if it is the default setting
- if ver_u.netloc.startswith('localhost'):
- auth_u = urlparse.urlparse(self.auth_url)
- # from original auth_url: scheme, netloc
- # from api_url: path, query (basically, the rest)
- url = urlparse.urlunparse((
- auth_u.scheme,
- auth_u.netloc,
- ver_u.path,
- ver_u.params,
- ver_u.query,
- ver_u.fragment,
- ))
- LOG.debug('Version URL updated: %s', url)
-
- return super(OSCGenericPassword, self).create_plugin(
- session=session,
- version=version,
- url=url,
- raw_status=raw_status,
- )
diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py
index 632e8b1..faa5511 100644
--- a/openstackclient/api/object_store_v1.py
+++ b/openstackclient/api/object_store_v1.py
@@ -17,11 +17,11 @@ import io
import logging
import os
+from osc_lib import utils
import six
from six.moves import urllib
from openstackclient.api import api
-from openstackclient.common import utils
class APIv1(api.BaseAPI):
diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py
index 3b0270a..63c5537 100644
--- a/openstackclient/common/availability_zone.py
+++ b/openstackclient/common/availability_zone.py
@@ -14,15 +14,19 @@
"""Availability Zone action implementations"""
import copy
+import logging
from novaclient import exceptions as nova_exceptions
+from osc_lib.command import command
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
def _xform_common_availability_zone(az, zone_info):
if hasattr(az, 'zoneState'):
zone_info['zone_status'] = ('available' if az.zoneState['available']
@@ -92,17 +96,20 @@ class ListAvailabilityZone(command.Lister):
'--compute',
action='store_true',
default=False,
- help='List compute availability zones')
+ help=_('List compute availability zones'),
+ )
parser.add_argument(
'--network',
action='store_true',
default=False,
- help='List network availability zones')
+ help=_('List network availability zones'),
+ )
parser.add_argument(
'--volume',
action='store_true',
default=False,
- help='List volume availability zones')
+ help=_('List volume availability zones'),
+ )
parser.add_argument(
'--long',
action='store_true',
@@ -115,11 +122,11 @@ class ListAvailabilityZone(command.Lister):
compute_client = self.app.client_manager.compute
try:
data = compute_client.availability_zones.list()
- except nova_exceptions.Forbidden as e: # policy doesn't allow
+ except nova_exceptions.Forbidden: # policy doesn't allow
try:
data = compute_client.availability_zones.list(detailed=False)
except Exception:
- raise e
+ raise
# Argh, the availability zones are not iterable...
result = []
@@ -133,11 +140,11 @@ class ListAvailabilityZone(command.Lister):
try:
data = volume_client.availability_zones.list()
except Exception as e:
- self.log.debug('Volume availability zone exception: ' + str(e))
+ LOG.debug('Volume availability zone exception: %s', e)
if parsed_args.volume:
- message = "Availability zones list not supported by " \
- "Block Storage API"
- self.log.warning(message)
+ message = _("Availability zones list not supported by "
+ "Block Storage API")
+ LOG.warning(message)
result = []
for zone in data:
@@ -151,11 +158,11 @@ class ListAvailabilityZone(command.Lister):
network_client.find_extension('Availability Zone',
ignore_missing=False)
except Exception as e:
- self.log.debug('Network availability zone exception: ' + str(e))
+ LOG.debug('Network availability zone exception: ', e)
if parsed_args.network:
- message = "Availability zones list not supported by " \
- "Network API"
- self.log.warning(message)
+ message = _("Availability zones list not supported by "
+ "Network API")
+ LOG.warning(message)
return []
result = []
diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py
new file mode 100644
index 0000000..895909e
--- /dev/null
+++ b/openstackclient/common/client_config.py
@@ -0,0 +1,229 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""OpenStackConfig subclass for argument compatibility"""
+
+import logging
+
+from os_client_config import config
+
+
+LOG = logging.getLogger(__name__)
+
+
+# Sublcass OpenStackConfig in order to munge config values
+# before auth plugins are loaded
+class OSC_Config(config.OpenStackConfig):
+
+ # TODO(dtroyer): Once os-client-config with pw_func argument is in
+ # global-requirements we can remove __init()__
+ def __init__(
+ self,
+ config_files=None,
+ vendor_files=None,
+ override_defaults=None,
+ force_ipv4=None,
+ envvar_prefix=None,
+ secure_files=None,
+ pw_func=None,
+ ):
+ ret = super(OSC_Config, self).__init__(
+ config_files=config_files,
+ vendor_files=vendor_files,
+ override_defaults=override_defaults,
+ force_ipv4=force_ipv4,
+ envvar_prefix=envvar_prefix,
+ secure_files=secure_files,
+ )
+
+ # NOTE(dtroyer): This will be pushed down into os-client-config
+ # The default is there is no callback, the calling
+ # application must specify what to use, typically
+ # it will be osc_lib.shell.prompt_for_password()
+ if '_pw_callback' not in vars(self):
+ # Set the default if it doesn't already exist
+ self._pw_callback = None
+ if pw_func is not None:
+ # Set the passed in value
+ self._pw_callback = pw_func
+
+ return ret
+
+ def _auth_select_default_plugin(self, config):
+ """Select a default plugin based on supplied arguments
+
+ Migrated from auth.select_auth_plugin()
+ """
+
+ identity_version = config.get('identity_api_version', '')
+
+ if config.get('username', None) and not config.get('auth_type', None):
+ if identity_version == '3':
+ config['auth_type'] = 'v3password'
+ elif identity_version.startswith('2'):
+ config['auth_type'] = 'v2password'
+ else:
+ # let keystoneauth figure it out itself
+ config['auth_type'] = 'password'
+ elif config.get('token', None) and not config.get('auth_type', None):
+ if identity_version == '3':
+ config['auth_type'] = 'v3token'
+ elif identity_version.startswith('2'):
+ config['auth_type'] = 'v2token'
+ else:
+ # let keystoneauth figure it out itself
+ config['auth_type'] = 'token'
+ else:
+ # The ultimate default is similar to the original behaviour,
+ # but this time with version discovery
+ if not config.get('auth_type', None):
+ config['auth_type'] = 'password'
+
+ LOG.debug("Auth plugin %s selected" % config['auth_type'])
+ return config
+
+ def _auth_v2_arguments(self, config):
+ """Set up v2-required arguments from v3 info
+
+ Migrated from auth.build_auth_params()
+ """
+
+ if ('auth_type' in config and config['auth_type'].startswith("v2")):
+ if 'project_id' in config['auth']:
+ config['auth']['tenant_id'] = config['auth']['project_id']
+ if 'project_name' in config['auth']:
+ config['auth']['tenant_name'] = config['auth']['project_name']
+ return config
+
+ def _auth_v2_ignore_v3(self, config):
+ """Remove v3 arguemnts if present for v2 plugin
+
+ Migrated from clientmanager.setup_auth()
+ """
+
+ # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID
+ # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then
+ # ignore all domain related configs.
+ if (config.get('identity_api_version', '').startswith('2') and
+ config.get('auth_type', None).endswith('password')):
+ domain_props = [
+ 'project_domain_id',
+ 'project_domain_name',
+ 'user_domain_id',
+ 'user_domain_name',
+ ]
+ for prop in domain_props:
+ if config['auth'].pop(prop, None) is not None:
+ LOG.warning("Ignoring domain related config " +
+ prop + " because identity API version is 2.0")
+ return config
+
+ def _auth_default_domain(self, config):
+ """Set a default domain from available arguments
+
+ Migrated from clientmanager.setup_auth()
+ """
+
+ identity_version = config.get('identity_api_version', '')
+ auth_type = config.get('auth_type', None)
+
+ # TODO(mordred): This is a usability improvement that's broadly useful
+ # We should port it back up into os-client-config.
+ default_domain = config.get('default_domain', None)
+ if (identity_version == '3' and
+ not auth_type.startswith('v2') and
+ default_domain):
+
+ # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is
+ # present, then do not change the behaviour. Otherwise, set the
+ # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability.
+ if (
+ not config['auth'].get('project_domain_id') and
+ not config['auth'].get('project_domain_name')
+ ):
+ config['auth']['project_domain_id'] = default_domain
+
+ # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present,
+ # then do not change the behaviour. Otherwise, set the
+ # USER_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability.
+ # NOTE(aloga): this should only be set if there is a username.
+ # TODO(dtroyer): Move this to os-client-config after the plugin has
+ # been loaded so we can check directly if the options are accepted.
+ if (
+ auth_type in ("password", "v3password", "v3totp") and
+ not config['auth'].get('user_domain_id') and
+ not config['auth'].get('user_domain_name')
+ ):
+ config['auth']['user_domain_id'] = default_domain
+ return config
+
+ def auth_config_hook(self, config):
+ """Allow examination of config values before loading auth plugin
+
+ OpenStackClient will override this to perform additional chacks
+ on auth_type.
+ """
+
+ config = self._auth_select_default_plugin(config)
+ config = self._auth_v2_arguments(config)
+ config = self._auth_v2_ignore_v3(config)
+ config = self._auth_default_domain(config)
+
+ LOG.debug("auth_config_hook(): %s" % config)
+ return config
+
+ def _validate_auth_ksc(self, config, cloud, fixed_argparse=None):
+ """Old compatibility hack for OSC, no longer needed/wanted"""
+ return config
+
+ def _validate_auth(self, config, loader, fixed_argparse=None):
+ """Validate auth plugin arguments"""
+ # May throw a keystoneauth1.exceptions.NoMatchingPlugin
+
+ plugin_options = loader.get_options()
+
+ for p_opt in plugin_options:
+ # if it's in config, win, move it and kill it from config dict
+ # if it's in config.auth but not in config we're good
+ # deprecated loses to current
+ # provided beats default, deprecated or not
+ winning_value = self._find_winning_auth_value(p_opt, config)
+ if not winning_value:
+ winning_value = self._find_winning_auth_value(
+ p_opt, config['auth'])
+
+ # Clean up after ourselves
+ for opt in [p_opt.name] + [o.name for o in p_opt.deprecated]:
+ opt = opt.replace('-', '_')
+ config.pop(opt, None)
+ config['auth'].pop(opt, None)
+
+ if winning_value:
+ # Prefer the plugin configuration dest value if the value's key
+ # is marked as depreciated.
+ if p_opt.dest is None:
+ config['auth'][p_opt.name.replace('-', '_')] = (
+ winning_value)
+ else:
+ config['auth'][p_opt.dest] = winning_value
+
+ # See if this needs a prompting
+ if (
+ 'prompt' in vars(p_opt) and
+ p_opt.prompt is not None and
+ p_opt.dest not in config['auth'] and
+ self._pw_callback is not None
+ ):
+ config['auth'][p_opt.dest] = self._pw_callback(p_opt.prompt)
+
+ return config
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py
index 9c2b320..57423ae 100644
--- a/openstackclient/common/clientmanager.py
+++ b/openstackclient/common/clientmanager.py
@@ -15,19 +15,12 @@
"""Manage access to the clients, including authenticating when needed."""
-import copy
import logging
import pkg_resources
import sys
-from oslo_utils import strutils
-import requests
-import six
-
-from openstackclient.api import auth
-from openstackclient.common import exceptions
-from openstackclient.common import session as osc_session
-from openstackclient.identity import client as identity_client
+from osc_lib import clientmanager
+from osc_lib import shell
LOG = logging.getLogger(__name__)
@@ -37,248 +30,44 @@ PLUGIN_MODULES = []
USER_AGENT = 'python-openstackclient'
-class ClientCache(object):
- """Descriptor class for caching created client handles."""
-
- def __init__(self, factory):
- self.factory = factory
- self._handle = None
-
- def __get__(self, instance, owner):
- # Tell the ClientManager to login to keystone
- if self._handle is None:
- try:
- self._handle = self.factory(instance)
- except AttributeError as err:
- # Make sure the failure propagates. Otherwise, the plugin just
- # quietly isn't there.
- new_err = exceptions.PluginAttributeError(err)
- six.reraise(new_err.__class__, new_err, sys.exc_info()[2])
- return self._handle
-
+class ClientManager(clientmanager.ClientManager):
+ """Manages access to API clients, including authentication
-class ClientManager(object):
- """Manages access to API clients, including authentication."""
+ Wrap osc_lib's ClientManager to maintain compatibility for the existing
+ plugin V2 interface. Some currently private attributes become public
+ in osc-lib so we need to maintain a transition period.
+ """
# A simple incrementing version for the plugin to know what is available
PLUGIN_INTERFACE_VERSION = "2"
- identity = ClientCache(identity_client.make_client)
-
- def __getattr__(self, name):
- # this is for the auth-related parameters.
- if name in ['_' + o.replace('-', '_')
- for o in auth.OPTIONS_LIST]:
- return self._auth_params[name[1:]]
-
- raise AttributeError(name)
-
def __init__(
self,
cli_options=None,
api_version=None,
- verify=True,
- pw_func=None,
):
- """Set up a ClientManager
-
- :param cli_options:
- Options collected from the command-line, environment, or wherever
- :param api_version:
- Dict of API versions: key is API name, value is the version
- :param verify:
- TLS certificate verification; may be a boolean to enable or disable
- server certificate verification, or a filename of a CA certificate
- bundle to be used in verification (implies True)
- :param pw_func:
- Callback function for asking the user for a password. The function
- takes an optional string for the prompt ('Password: ' on None) and
- returns a string containing the password
- """
-
- self._cli_options = cli_options
- self._api_version = api_version
- self._pw_callback = pw_func
- self._url = self._cli_options.auth.get('url')
- self._region_name = self._cli_options.region_name
- self._interface = self._cli_options.interface
-
- self.timing = self._cli_options.timing
-
- self._auth_ref = None
- self.session = None
-
- # verify is the Requests-compatible form
- self._verify = verify
- # also store in the form used by the legacy client libs
- self._cacert = None
- if isinstance(verify, bool):
- self._insecure = not verify
- else:
- self._cacert = verify
- self._insecure = False
-
- # Set up client certificate and key
- # NOTE(cbrandily): This converts client certificate/key to requests
- # cert argument: None (no client certificate), a path
- # to client certificate or a tuple with client
- # certificate/key paths.
- self._cert = self._cli_options.cert
- if self._cert and self._cli_options.key:
- self._cert = self._cert, self._cli_options.key
-
- # Get logging from root logger
- root_logger = logging.getLogger('')
- LOG.setLevel(root_logger.getEffectiveLevel())
-
- # NOTE(gyee): use this flag to indicate whether auth setup has already
- # been completed. If so, do not perform auth setup again. The reason
- # we need this flag is that we want to be able to perform auth setup
- # outside of auth_ref as auth_ref itself is a property. We can not
- # retrofit auth_ref to optionally skip scope check. Some operations
- # do not require a scoped token. In those cases, we call setup_auth
- # prior to dereferrencing auth_ref.
- self._auth_setup_completed = False
-
- def setup_auth(self, required_scope=True):
- """Set up authentication
-
- :param required_scope: indicate whether a scoped token is required
-
- This is deferred until authentication is actually attempted because
- it gets in the way of things that do not require auth.
- """
-
- if self._auth_setup_completed:
- return
-
- # If no auth type is named by the user, select one based on
- # the supplied options
- self.auth_plugin_name = auth.select_auth_plugin(self._cli_options)
-
- # Basic option checking to avoid unhelpful error messages
- auth.check_valid_auth_options(self._cli_options,
- self.auth_plugin_name,
- required_scope=required_scope)
-
- # Horrible hack alert...must handle prompt for null password if
- # password auth is requested.
- if (self.auth_plugin_name.endswith('password') and
- not self._cli_options.auth.get('password')):
- self._cli_options.auth['password'] = self._pw_callback()
-
- (auth_plugin, self._auth_params) = auth.build_auth_params(
- self.auth_plugin_name,
- self._cli_options,
+ super(ClientManager, self).__init__(
+ cli_options=cli_options,
+ api_version=api_version,
+ # TODO(dtroyer): Remove this when osc-lib 1.2 is released
+ pw_func=shell.prompt_for_password,
)
- # TODO(mordred): This is a usability improvement that's broadly useful
- # We should port it back up into os-client-config.
- default_domain = self._cli_options.default_domain
- # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is
- # present, then do not change the behaviour. Otherwise, set the
- # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability.
- if (self._api_version.get('identity') == '3' and
- self.auth_plugin_name.endswith('password') and
- not self._auth_params.get('project_domain_id') and
- not self.auth_plugin_name.startswith('v2') and
- not self._auth_params.get('project_domain_name')):
- self._auth_params['project_domain_id'] = default_domain
-
- # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present,
- # then do not change the behaviour. Otherwise, set the USER_DOMAIN_ID
- # to 'OS_DEFAULT_DOMAIN' for better usability.
- if (self._api_version.get('identity') == '3' and
- self.auth_plugin_name.endswith('password') and
- not self.auth_plugin_name.startswith('v2') and
- not self._auth_params.get('user_domain_id') and
- not self._auth_params.get('user_domain_name')):
- self._auth_params['user_domain_id'] = default_domain
-
- # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID
- # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then
- # ignore all domain related configs.
- if (self._api_version.get('identity') == '2.0' and
- self.auth_plugin_name.endswith('password')):
- domain_props = ['project_domain_name', 'project_domain_id',
- 'user_domain_name', 'user_domain_id']
- for prop in domain_props:
- if self._auth_params.pop(prop, None) is not None:
- LOG.warning("Ignoring domain related configs " +
- prop + " because identity API version is 2.0")
-
- # For compatibility until all clients can be updated
- if 'project_name' in self._auth_params:
- self._project_name = self._auth_params['project_name']
- elif 'tenant_name' in self._auth_params:
- self._project_name = self._auth_params['tenant_name']
-
- LOG.info('Using auth plugin: %s', self.auth_plugin_name)
- LOG.debug('Using parameters %s',
- strutils.mask_password(self._auth_params))
- self.auth = auth_plugin.load_from_options(**self._auth_params)
- # needed by SAML authentication
- request_session = requests.session()
- self.session = osc_session.TimingSession(
- auth=self.auth,
- session=request_session,
- verify=self._verify,
- cert=self._cert,
- user_agent=USER_AGENT,
- )
-
- self._auth_setup_completed = True
-
- @property
- def auth_ref(self):
- """Dereference will trigger an auth if it hasn't already"""
- if not self._auth_ref:
- self.setup_auth()
- LOG.debug("Get auth_ref")
- self._auth_ref = self.auth.get_auth_ref(self.session)
- return self._auth_ref
+ # TODO(dtroyer): For compatibility; mark this for removal when plugin
+ # interface v2 is removed
+ self._region_name = self.region_name
+ self._interface = self.interface
+ self._cacert = self.cacert
+ self._insecure = not self.verify
def is_network_endpoint_enabled(self):
"""Check if the network endpoint is enabled"""
- # Trigger authentication necessary to determine if the network
- # endpoint is enabled.
- if self.auth_ref:
- service_catalog = self.auth_ref.service_catalog
- else:
- service_catalog = None
- # Assume that the network endpoint is enabled.
- network_endpoint_enabled = True
- if service_catalog:
- if 'network' in service_catalog.get_endpoints():
- LOG.debug("Network endpoint in service catalog")
- else:
- LOG.debug("No network endpoint in service catalog")
- network_endpoint_enabled = False
- else:
- LOG.debug("No service catalog, assuming network endpoint enabled")
- return network_endpoint_enabled
-
- def get_endpoint_for_service_type(self, service_type, region_name=None,
- interface='public'):
- """Return the endpoint URL for the service type."""
- if not interface:
- interface = 'public'
- # See if we are using password flow auth, i.e. we have a
- # service catalog to select endpoints from
- if self.auth_ref:
- endpoint = self.auth_ref.service_catalog.url_for(
- service_type=service_type,
- region_name=region_name,
- endpoint_type=interface,
- )
- else:
- # Get the passed endpoint directly from the auth plugin
- endpoint = self.auth.get_endpoint(self.session,
- interface=interface)
- return endpoint
- def get_configuration(self):
- return copy.deepcopy(self._cli_options.config)
+ # NOTE(dtroyer): is_service_available() can also return None if
+ # there is no Service Catalog, callers here are
+ # not expecting that so fold None into True to
+ # use Network API by default
+ return self.is_service_available('network') is not False
# Plugin Support
@@ -298,9 +87,9 @@ def get_plugin_modules(group):
# Add the plugin to the ClientManager
setattr(
- ClientManager,
+ clientmanager.ClientManager,
module.API_NAME,
- ClientCache(
+ clientmanager.ClientCache(
getattr(sys.modules[ep.module_name], 'make_client', None)
),
)
diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py
index 144a0db..29c1534 100644
--- a/openstackclient/common/command.py
+++ b/openstackclient/common/command.py
@@ -12,45 +12,15 @@
# License for the specific language governing permissions and limitations
# under the License.
-import abc
-import logging
+# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release
+# or Jun 2017.
-from cliff import command
-from cliff import lister
-from cliff import show
-import six
+import sys
-from openstackclient.common import exceptions
-from openstackclient.i18n import _
+from osc_lib.command.command import * # noqa
-class CommandMeta(abc.ABCMeta):
-
- def __new__(mcs, name, bases, cls_dict):
- if 'log' not in cls_dict:
- cls_dict['log'] = logging.getLogger(
- cls_dict['__module__'] + '.' + name)
- return super(CommandMeta, mcs).__new__(mcs, name, bases, cls_dict)
-
-
-@six.add_metaclass(CommandMeta)
-class Command(command.Command):
-
- def run(self, parsed_args):
- self.log.debug('run(%s)', parsed_args)
- return super(Command, self).run(parsed_args)
-
- def validate_os_beta_command_enabled(self):
- if not self.app.options.os_beta_command:
- msg = _('Caution: This is a beta command and subject to '
- 'change. Use global option --os-beta-command '
- 'to enable this command.')
- raise exceptions.CommandError(msg)
-
-
-class Lister(Command, lister.Lister):
- pass
-
-
-class ShowOne(Command, show.ShowOne):
- pass
+sys.stderr.write(
+ "WARNING: %s is deprecated and will be removed after Jun 2017. "
+ "Please use osc_lib.command.command\n" % __name__
+)
diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py
index a70e4d1..016e919 100644
--- a/openstackclient/common/configuration.py
+++ b/openstackclient/common/configuration.py
@@ -13,9 +13,11 @@
"""Configuration action implementations"""
+from keystoneauth1.loading import base
+from osc_lib.command import command
import six
-from openstackclient.common import command
+from openstackclient.i18n import _
REDACTED = "<redacted>"
@@ -31,24 +33,25 @@ class ShowConfiguration(command.ShowOne):
dest="mask",
action="store_true",
default=True,
- help="Attempt to mask passwords (default)",
+ help=_("Attempt to mask passwords (default)"),
)
mask_group.add_argument(
"--unmask",
dest="mask",
action="store_false",
- help="Show password in clear text",
+ help=_("Show password in clear text"),
)
return parser
def take_action(self, parsed_args):
+ auth_plg_name = self.app.client_manager.auth_plugin_name
+ secret_opts = [o.dest for o in base.get_plugin_options(auth_plg_name)
+ if o.secret]
+
info = self.app.client_manager.get_configuration()
for key, value in six.iteritems(info.pop('auth', {})):
- if parsed_args.mask:
- if 'password' in key.lower():
- value = REDACTED
- if 'token' in key.lower():
+ if parsed_args.mask and key.lower() in secret_opts:
value = REDACTED
info['auth.' + key] = value
return zip(*sorted(six.iteritems(info)))
diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py
index bdc33dd..7124074 100644
--- a/openstackclient/common/exceptions.py
+++ b/openstackclient/common/exceptions.py
@@ -1,5 +1,3 @@
-# Copyright 2012-2013 OpenStack, LLC.
-#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
@@ -13,105 +11,15 @@
# under the License.
#
-"""Exception definitions."""
-
-
-class CommandError(Exception):
- pass
-
-
-class AuthorizationFailure(Exception):
- pass
-
-
-class PluginAttributeError(Exception):
- """A plugin threw an AttributeError while being lazily loaded."""
- # This *must not* inherit from AttributeError;
- # that would defeat the whole purpose.
- pass
-
-
-class NoTokenLookupException(Exception):
- """This does not support looking up endpoints from an existing token."""
- pass
-
-
-class EndpointNotFound(Exception):
- """Could not find Service or Region in Service Catalog."""
- pass
-
-
-class UnsupportedVersion(Exception):
- """The user is trying to use an unsupported version of the API"""
- pass
-
-
-class ClientException(Exception):
- """The base exception class for all exceptions this library raises."""
+# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release
+# or Jun 2017.
- def __init__(self, code, message=None, details=None):
- self.code = code
- self.message = message or self.__class__.message
- self.details = details
+import sys
- def __str__(self):
- return "%s (HTTP %s)" % (self.message, self.code)
+from osc_lib.exceptions import * # noqa
-class BadRequest(ClientException):
- """HTTP 400 - Bad request: you sent some malformed data."""
- http_status = 400
- message = "Bad request"
-
-
-class Unauthorized(ClientException):
- """HTTP 401 - Unauthorized: bad credentials."""
- http_status = 401
- message = "Unauthorized"
-
-
-class Forbidden(ClientException):
- """HTTP 403 - Forbidden: not authorized to access to this resource."""
- http_status = 403
- message = "Forbidden"
-
-
-class NotFound(ClientException):
- """HTTP 404 - Not found"""
- http_status = 404
- message = "Not found"
-
-
-class Conflict(ClientException):
- """HTTP 409 - Conflict"""
- http_status = 409
- message = "Conflict"
-
-
-class OverLimit(ClientException):
- """HTTP 413 - Over limit: reached the API limits for this time period."""
- http_status = 413
- message = "Over limit"
-
-
-# NotImplemented is a python keyword.
-class HTTPNotImplemented(ClientException):
- """HTTP 501 - Not Implemented: server does not support this operation."""
- http_status = 501
- message = "Not Implemented"
-
-
-# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__()
-# so we can do this:
-# _code_map = dict((c.http_status, c)
-# for c in ClientException.__subclasses__())
-#
-# Instead, we have to hardcode it:
-_code_map = dict((c.http_status, c) for c in [
- BadRequest,
- Unauthorized,
- Forbidden,
- NotFound,
- OverLimit,
- HTTPNotImplemented
-])
+sys.stderr.write(
+ "WARNING: %s is deprecated and will be removed after Jun 2017. "
+ "Please use osc_lib.exceptions\n" % __name__
+)
diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py
index 8b556b4..de48001 100644
--- a/openstackclient/common/extension.py
+++ b/openstackclient/common/extension.py
@@ -16,9 +16,15 @@
"""Extension action implementations"""
import itertools
+import logging
-from openstackclient.common import command
-from openstackclient.common import utils
+from osc_lib.command import command
+from osc_lib import utils
+
+from openstackclient.i18n import _
+
+
+LOG = logging.getLogger(__name__)
class ListExtension(command.Lister):
@@ -30,27 +36,32 @@ class ListExtension(command.Lister):
'--compute',
action='store_true',
default=False,
- help='List extensions for the Compute API')
+ help=_('List extensions for the Compute API'),
+ )
parser.add_argument(
'--identity',
action='store_true',
default=False,
- help='List extensions for the Identity API')
+ help=_('List extensions for the Identity API'),
+ )
parser.add_argument(
'--network',
action='store_true',
default=False,
- help='List extensions for the Network API')
+ help=_('List extensions for the Network API'),
+ )
parser.add_argument(
'--volume',
action='store_true',
default=False,
- help='List extensions for the Block Storage API')
+ help=_('List extensions for the Block Storage API'),
+ )
parser.add_argument(
'--long',
action='store_true',
default=False,
- help='List additional fields in output')
+ help=_('List additional fields in output'),
+ )
return parser
def take_action(self, parsed_args):
@@ -73,24 +84,25 @@ class ListExtension(command.Lister):
try:
data += identity_client.extensions.list()
except Exception:
- message = "Extensions list not supported by Identity API"
- self.log.warning(message)
+ message = _("Extensions list not supported by Identity API")
+ LOG.warning(message)
if parsed_args.compute or show_all:
compute_client = self.app.client_manager.compute
try:
data += compute_client.list_extensions.show_all()
except Exception:
- message = "Extensions list not supported by Compute API"
- self.log.warning(message)
+ message = _("Extensions list not supported by Compute API")
+ LOG.warning(message)
if parsed_args.volume or show_all:
volume_client = self.app.client_manager.volume
try:
data += volume_client.list_extensions.show_all()
except Exception:
- message = "Extensions list not supported by Block Storage API"
- self.log.warning(message)
+ message = _("Extensions list not supported by "
+ "Block Storage API")
+ LOG.warning(message)
# Resource classes for the above
extension_tuples = (
@@ -118,7 +130,7 @@ class ListExtension(command.Lister):
dict_tuples
)
except Exception:
- message = "Extensions list not supported by Network API"
- self.log.warning(message)
+ message = _("Extensions list not supported by Network API")
+ LOG.warning(message)
return (columns, extension_tuples)
diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py
index 1f87abf..f7aa82f 100644
--- a/openstackclient/common/limits.py
+++ b/openstackclient/common/limits.py
@@ -17,8 +17,10 @@
import itertools
-from openstackclient.common import command
-from openstackclient.common import utils
+from osc_lib.command import command
+from osc_lib import utils
+
+from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
@@ -33,30 +35,33 @@ class ShowLimits(command.Lister):
dest="is_absolute",
action="store_true",
default=False,
- help="Show absolute limits")
+ help=_("Show absolute limits"),
+ )
type_group.add_argument(
"--rate",
dest="is_rate",
action="store_true",
default=False,
- help="Show rate limits")
+ help=_("Show rate limits"),
+ )
parser.add_argument(
"--reserved",
dest="is_reserved",
action="store_true",
default=False,
- help="Include reservations count [only valid with --absolute]")
+ help=_("Include reservations count [only valid with --absolute]"),
+ )
parser.add_argument(
'--project',
metavar='<project>',
- help='Show limits for a specific project (name or ID)'
- ' [only valid with --absolute]',
+ help=_('Show limits for a specific project (name or ID)'
+ ' [only valid with --absolute]'),
)
parser.add_argument(
'--domain',
metavar='<domain>',
- help='Domain the project belongs to (name or ID)'
- ' [only valid with --absolute]',
+ help=_('Domain the project belongs to (name or ID)'
+ ' [only valid with --absolute]'),
)
return parser
diff --git a/openstackclient/common/logs.py b/openstackclient/common/logs.py
index 221c599..8aa97d5 100644
--- a/openstackclient/common/logs.py
+++ b/openstackclient/common/logs.py
@@ -11,188 +11,16 @@
# under the License.
#
-"""Application logging"""
+# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release
+# or Jun 2017.
-import logging
import sys
-import warnings
+from osc_lib.logs import * # noqa
+from osc_lib.logs import _FileFormatter # noqa
-def get_loggers():
- loggers = {}
- for logkey in logging.Logger.manager.loggerDict.keys():
- loggers[logkey] = logging.getLevelName(logging.getLogger(logkey).level)
- return loggers
-
-def log_level_from_options(options):
- # if --debug, --quiet or --verbose is not specified,
- # the default logging level is warning
- log_level = logging.WARNING
- if options.verbose_level == 0:
- # --quiet
- log_level = logging.ERROR
- elif options.verbose_level == 2:
- # One --verbose
- log_level = logging.INFO
- elif options.verbose_level >= 3:
- # Two or more --verbose
- log_level = logging.DEBUG
- return log_level
-
-
-def log_level_from_string(level_string):
- log_level = {
- 'critical': logging.CRITICAL,
- 'error': logging.ERROR,
- 'warning': logging.WARNING,
- 'info': logging.INFO,
- 'debug': logging.DEBUG,
- }.get(level_string, logging.WARNING)
- return log_level
-
-
-def log_level_from_config(config):
- # Check the command line option
- verbose_level = config.get('verbose_level')
- if config.get('debug', False):
- verbose_level = 3
- if verbose_level == 0:
- verbose_level = 'error'
- elif verbose_level == 1:
- # If a command line option has not been specified, check the
- # configuration file
- verbose_level = config.get('log_level', 'warning')
- elif verbose_level == 2:
- verbose_level = 'info'
- else:
- verbose_level = 'debug'
- return log_level_from_string(verbose_level)
-
-
-def set_warning_filter(log_level):
- if log_level == logging.ERROR:
- warnings.simplefilter("ignore")
- elif log_level == logging.WARNING:
- warnings.simplefilter("ignore")
- elif log_level == logging.INFO:
- warnings.simplefilter("once")
-
-
-class _FileFormatter(logging.Formatter):
- """Customize the logging format for logging handler"""
- _LOG_MESSAGE_BEGIN = (
- '%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s ')
- _LOG_MESSAGE_CONTEXT = '[%(cloud)s %(username)s %(project)s] '
- _LOG_MESSAGE_END = '%(message)s'
- _LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
-
- def __init__(self, options=None, config=None, **kwargs):
- context = {}
- if options:
- context = {
- 'cloud': getattr(options, 'cloud', ''),
- 'project': getattr(options, 'os_project_name', ''),
- 'username': getattr(options, 'username', ''),
- }
- elif config:
- context = {
- 'cloud': config.config.get('cloud', ''),
- 'project': config.auth.get('project_name', ''),
- 'username': config.auth.get('username', ''),
- }
- if context:
- self.fmt = (self._LOG_MESSAGE_BEGIN +
- (self._LOG_MESSAGE_CONTEXT % context) +
- self._LOG_MESSAGE_END)
- else:
- self.fmt = self._LOG_MESSAGE_BEGIN + self._LOG_MESSAGE_END
- logging.Formatter.__init__(self, self.fmt, self._LOG_DATE_FORMAT)
-
-
-class LogConfigurator(object):
-
- _CONSOLE_MESSAGE_FORMAT = '%(message)s'
-
- def __init__(self, options):
- self.root_logger = logging.getLogger('')
- self.root_logger.setLevel(logging.DEBUG)
-
- # Force verbose_level 3 on --debug
- self.dump_trace = False
- if options.debug:
- options.verbose_level = 3
- self.dump_trace = True
-
- # Always send higher-level messages to the console via stderr
- self.console_logger = logging.StreamHandler(sys.stderr)
- log_level = log_level_from_options(options)
- self.console_logger.setLevel(log_level)
- formatter = logging.Formatter(self._CONSOLE_MESSAGE_FORMAT)
- self.console_logger.setFormatter(formatter)
- self.root_logger.addHandler(self.console_logger)
-
- # Set the warning filter now
- set_warning_filter(log_level)
-
- # Set up logging to a file
- self.file_logger = None
- log_file = options.log_file
- if log_file:
- self.file_logger = logging.FileHandler(filename=log_file)
- self.file_logger.setFormatter(_FileFormatter(options=options))
- self.file_logger.setLevel(log_level)
- self.root_logger.addHandler(self.file_logger)
-
- # Requests logs some stuff at INFO that we don't want
- # unless we have DEBUG
- requests_log = logging.getLogger("requests")
-
- # Other modules we don't want DEBUG output for
- cliff_log = logging.getLogger('cliff')
- stevedore_log = logging.getLogger('stevedore')
- iso8601_log = logging.getLogger("iso8601")
-
- if options.debug:
- # --debug forces traceback
- requests_log.setLevel(logging.DEBUG)
- else:
- requests_log.setLevel(logging.ERROR)
-
- cliff_log.setLevel(logging.ERROR)
- stevedore_log.setLevel(logging.ERROR)
- iso8601_log.setLevel(logging.ERROR)
-
- def configure(self, cloud_config):
- log_level = log_level_from_config(cloud_config.config)
- set_warning_filter(log_level)
- self.dump_trace = cloud_config.config.get('debug', self.dump_trace)
- self.console_logger.setLevel(log_level)
-
- log_file = cloud_config.config.get('log_file')
- if log_file:
- if not self.file_logger:
- self.file_logger = logging.FileHandler(filename=log_file)
- formatter = _FileFormatter(cloud_config=cloud_config)
- self.file_logger.setFormatter(formatter)
- self.file_logger.setFormatter(_FileFormatter(config=cloud_config))
- self.file_logger.setLevel(log_level)
- self.root_logger.addHandler(self.file_logger)
-
- logconfig = cloud_config.config.get('logging')
- if logconfig:
- highest_level = logging.NOTSET
- for k in logconfig.keys():
- level = log_level_from_string(logconfig[k])
- logging.getLogger(k).setLevel(level)
- if (highest_level < level):
- highest_level = level
- self.console_logger.setLevel(highest_level)
- if self.file_logger:
- self.file_logger.setLevel(highest_level)
- # loggers that are not set will use the handler level, so we
- # need to set the global level for all the loggers
- for logkey in logging.Logger.manager.loggerDict.keys():
- logger = logging.getLogger(logkey)
- if logger.level == logging.NOTSET:
- logger.setLevel(log_level)
+sys.stderr.write(
+ "WARNING: %s is deprecated and will be removed after Jun 2017. "
+ "Please use osc_lib.logs\n" % __name__
+)
diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py
index 30c67c6..7c5fcd5 100644
--- a/openstackclient/common/module.py
+++ b/openstackclient/common/module.py
@@ -15,11 +15,13 @@
"""Module action implementation"""
-import six
import sys
-from openstackclient.common import command
-from openstackclient.common import utils
+from osc_lib.command import command
+from osc_lib import utils
+import six
+
+from openstackclient.i18n import _
class ListCommand(command.Lister):
@@ -61,7 +63,7 @@ class ListModule(command.ShowOne):
'--all',
action='store_true',
default=False,
- help='Show all modules that have version information',
+ help=_('Show all modules that have version information'),
)
return parser
diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py
index 77798f9..fa5148e 100644
--- a/openstackclient/common/parseractions.py
+++ b/openstackclient/common/parseractions.py
@@ -1,5 +1,3 @@
-# Copyright 2013 OpenStack Foundation
-#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
@@ -13,150 +11,15 @@
# under the License.
#
-"""argparse Custom Actions"""
-
-import argparse
-
-from openstackclient.i18n import _
-
-
-class KeyValueAction(argparse.Action):
- """A custom action to parse arguments as key=value pairs
-
- Ensures that ``dest`` is a dict
- """
-
- def __call__(self, parser, namespace, values, option_string=None):
- # Make sure we have an empty dict rather than None
- if getattr(namespace, self.dest, None) is None:
- setattr(namespace, self.dest, {})
-
- # Add value if an assignment else remove it
- if '=' in values:
- getattr(namespace, self.dest, {}).update([values.split('=', 1)])
- else:
- getattr(namespace, self.dest, {}).pop(values, None)
-
-
-class MultiKeyValueAction(argparse.Action):
- """A custom action to parse arguments as key1=value1,key2=value2 pairs
-
- Ensure that ``dest`` is a list. The list will finally contain multiple
- dicts, with key=value pairs in them.
-
- NOTE: The arguments string should be a comma separated key-value pairs.
- And comma(',') and equal('=') may not be used in the key or value.
- """
-
- def __init__(self, option_strings, dest, nargs=None,
- required_keys=None, optional_keys=None, **kwargs):
- """Initialize the action object, and parse customized options
-
- Required keys and optional keys can be specified when initializing
- the action to enable the key validation. If none of them specified,
- the key validation will be skipped.
-
- :param required_keys: a list of required keys
- :param optional_keys: a list of optional keys
- """
- if nargs:
- raise ValueError("Parameter 'nargs' is not allowed, but got %s"
- % nargs)
-
- super(MultiKeyValueAction, self).__init__(option_strings,
- dest, **kwargs)
-
- # required_keys: A list of keys that is required. None by default.
- if required_keys and not isinstance(required_keys, list):
- raise TypeError("'required_keys' must be a list")
- self.required_keys = set(required_keys or [])
-
- # optional_keys: A list of keys that is optional. None by default.
- if optional_keys and not isinstance(optional_keys, list):
- raise TypeError("'optional_keys' must be a list")
- self.optional_keys = set(optional_keys or [])
-
- def __call__(self, parser, namespace, values, metavar=None):
- # Make sure we have an empty list rather than None
- if getattr(namespace, self.dest, None) is None:
- setattr(namespace, self.dest, [])
-
- params = {}
- for kv in values.split(','):
- # Add value if an assignment else raise ArgumentTypeError
- if '=' in kv:
- params.update([kv.split('=', 1)])
- else:
- msg = ("Expected key=value pairs separated by comma, "
- "but got: %s" % (str(kv)))
- raise argparse.ArgumentTypeError(msg)
-
- # Check key validation
- valid_keys = self.required_keys | self.optional_keys
- if valid_keys:
- invalid_keys = [k for k in params if k not in valid_keys]
- if invalid_keys:
- msg = _("Invalid keys %(invalid_keys)s specified.\n"
- "Valid keys are: %(valid_keys)s.")
- raise argparse.ArgumentTypeError(
- msg % {'invalid_keys': ', '.join(invalid_keys),
- 'valid_keys': ', '.join(valid_keys)}
- )
-
- if self.required_keys:
- missing_keys = [k for k in self.required_keys if k not in params]
- if missing_keys:
- msg = _("Missing required keys %(missing_keys)s.\n"
- "Required keys are: %(required_keys)s.")
- raise argparse.ArgumentTypeError(
- msg % {'missing_keys': ', '.join(missing_keys),
- 'required_keys': ', '.join(self.required_keys)}
- )
-
- # Update the dest dict
- getattr(namespace, self.dest, []).append(params)
-
-
-class RangeAction(argparse.Action):
- """A custom action to parse a single value or a range of values
-
- Parses single integer values or a range of integer values delimited
- by a colon and returns a tuple of integers:
- '4' sets ``dest`` to (4, 4)
- '6:9' sets ``dest`` to (6, 9)
- """
-
- def __call__(self, parser, namespace, values, option_string=None):
- range = values.split(':')
- if len(range) == 0:
- # Nothing passed, return a zero default
- setattr(namespace, self.dest, (0, 0))
- elif len(range) == 1:
- # Only a single value is present
- setattr(namespace, self.dest, (int(range[0]), int(range[0])))
- elif len(range) == 2:
- # Range of two values
- if int(range[0]) <= int(range[1]):
- setattr(namespace, self.dest, (int(range[0]), int(range[1])))
- else:
- msg = "Invalid range, %s is not less than %s" % \
- (range[0], range[1])
- raise argparse.ArgumentError(self, msg)
- else:
- # Too many values
- msg = "Invalid range, too many values"
- raise argparse.ArgumentError(self, msg)
+# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release
+# or Jun 2017.
+import sys
-class NonNegativeAction(argparse.Action):
- """A custom action to check whether the value is non-negative or not
+from osc_lib.cli.parseractions import * # noqa
- Ensures the value is >= 0.
- """
- def __call__(self, parser, namespace, values, option_string=None):
- if int(values) >= 0:
- setattr(namespace, self.dest, values)
- else:
- msg = "%s expected a non-negative integer" % (str(option_string))
- raise argparse.ArgumentTypeError(msg)
+sys.stderr.write(
+ "WARNING: %s is deprecated and will be removed after Jun 2017. "
+ "Please use osc_lib.cli.parseractions\n" % __name__
+)
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index f85d550..5d53171 100644
--- a/openstackclient/common/quota.py
+++ b/openstackclient/common/quota.py
@@ -16,11 +16,13 @@
"""Quota action implementations"""
import itertools
-import six
import sys
-from openstackclient.common import command
-from openstackclient.common import utils
+from osc_lib.command import command
+from osc_lib import utils
+import six
+
+from openstackclient.i18n import _
# List the quota items, map the internal argument name to the option
@@ -36,6 +38,8 @@ COMPUTE_QUOTAS = {
'key_pairs': 'key-pairs',
'metadata_items': 'properties',
'ram': 'ram',
+ 'server_groups': 'server-groups',
+ 'server_group_members': 'server-group-members',
}
VOLUME_QUOTAS = {
@@ -84,14 +88,14 @@ class SetQuota(command.Command):
parser.add_argument(
'project',
metavar='<project/class>',
- help='Set quotas for this project or class (name/ID)',
+ help=_('Set quotas for this project or class (name/ID)'),
)
parser.add_argument(
'--class',
dest='quota_class',
action='store_true',
default=False,
- help='Set quotas for <class>',
+ help=_('Set quotas for <class>'),
)
for k, v in self._build_options_list():
parser.add_argument(
@@ -99,12 +103,12 @@ class SetQuota(command.Command):
metavar='<%s>' % v,
dest=k,
type=int,
- help='New value for the %s quota' % v,
+ help=_('New value for the %s quota') % v,
)
parser.add_argument(
'--volume-type',
metavar='<volume-type>',
- help='Set quotas for a specific <volume-type>',
+ help=_('Set quotas for a specific <volume-type>'),
)
return parser
@@ -140,11 +144,6 @@ class SetQuota(command.Command):
if value is not None:
compute_kwargs[k] = value
- if (compute_kwargs == {} and volume_kwargs == {}
- and network_kwargs == {}):
- sys.stderr.write("No quotas updated\n")
- return
-
if parsed_args.project:
project = utils.find_resource(
identity_client.projects,
@@ -187,7 +186,7 @@ class ShowQuota(command.ShowOne):
'project',
metavar='<project/class>',
nargs='?',
- help='Show quotas for this project or class (name or ID)',
+ help=_('Show quotas for this project or class (name or ID)'),
)
type_group = parser.add_mutually_exclusive_group()
type_group.add_argument(
@@ -195,14 +194,14 @@ class ShowQuota(command.ShowOne):
dest='quota_class',
action='store_true',
default=False,
- help='Show quotas for <class>',
+ help=_('Show quotas for <class>'),
)
type_group.add_argument(
'--default',
dest='default',
action='store_true',
default=False,
- help='Show default quotas for <project>'
+ help=_('Show default quotas for <project>')
)
return parser
@@ -234,7 +233,7 @@ class ShowQuota(command.ShowOne):
if type(e).__name__ == 'EndpointNotFound':
return {}
else:
- raise e
+ raise
return quota._info
def get_network_quota(self, parsed_args):
diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py
index 71c2fec..facbec3 100644
--- a/openstackclient/common/timing.py
+++ b/openstackclient/common/timing.py
@@ -11,31 +11,15 @@
# under the License.
#
-"""Timing Implementation"""
+# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release
+# or Jun 2017.
-from openstackclient.common import command
+import sys
+from osc_lib.command.timing import * # noqa
-class Timing(command.Lister):
- """Show timing data"""
- def take_action(self, parsed_args):
- column_headers = (
- 'URL',
- 'Seconds',
- )
-
- results = []
- total = 0.0
- for url, td in self.app.timing_data:
- # NOTE(dtroyer): Take the long way here because total_seconds()
- # was added in py27.
- sec = (td.microseconds + (td.seconds + td.days *
- 86400) * 1e6) / 1e6
- total += sec
- results.append((url, sec))
- results.append(('Total', total))
- return (
- column_headers,
- results,
- )
+sys.stderr.write(
+ "WARNING: %s is deprecated and will be removed after Jun 2017. "
+ "Please use osc_lib.command.timing\n" % __name__
+)
diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py
index daa65c2..73cd3dc 100644
--- a/openstackclient/common/utils.py
+++ b/openstackclient/common/utils.py
@@ -1,5 +1,3 @@
-# Copyright 2012-2013 OpenStack Foundation
-#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
@@ -13,410 +11,15 @@
# under the License.
#
-"""Common client utilities"""
-
-import getpass
-import logging
-import os
-import six
-import time
-
-from oslo_utils import importutils
-
-from openstackclient.common import exceptions
-
-
-def find_resource(manager, name_or_id, **kwargs):
- """Helper for the _find_* methods.
-
- :param manager: A client manager class
- :param name_or_id: The resource we are trying to find
- :param kwargs: To be used in calling .find()
- :rtype: The found resource
-
- This method will attempt to find a resource in a variety of ways.
- Primarily .get() methods will be called with `name_or_id` as an integer
- value, and tried again as a string value.
-
- If both fail, then a .find() is attempted, which is essentially calling
- a .list() function with a 'name' query parameter that is set to
- `name_or_id`.
-
- Lastly, if any kwargs are passed in, they will be treated as additional
- query parameters. This is particularly handy in the case of finding
- resources in a domain.
-
- """
-
- # Try to get entity as integer id
- try:
- if isinstance(name_or_id, int) or name_or_id.isdigit():
- return manager.get(int(name_or_id), **kwargs)
- # FIXME(dtroyer): The exception to catch here is dependent on which
- # client library the manager passed in belongs to.
- # Eventually this should be pulled from a common set
- # of client exceptions.
- except Exception as ex:
- if type(ex).__name__ == 'NotFound':
- pass
- else:
- raise
-
- # Try directly using the passed value
- try:
- return manager.get(name_or_id, **kwargs)
- except Exception:
- pass
-
- if len(kwargs) == 0:
- kwargs = {}
-
- try:
- # Prepare the kwargs for calling find
- if 'NAME_ATTR' in manager.resource_class.__dict__:
- # novaclient does this for oddball resources
- kwargs[manager.resource_class.NAME_ATTR] = name_or_id
- else:
- kwargs['name'] = name_or_id
- except Exception:
- pass
-
- # finally try to find entity by name
- try:
- return manager.find(**kwargs)
- # FIXME(dtroyer): The exception to catch here is dependent on which
- # client library the manager passed in belongs to.
- # Eventually this should be pulled from a common set
- # of client exceptions.
- except Exception as ex:
- if type(ex).__name__ == 'NotFound':
- msg = "No %s with a name or ID of '%s' exists." % \
- (manager.resource_class.__name__.lower(), name_or_id)
- raise exceptions.CommandError(msg)
- if type(ex).__name__ == 'NoUniqueMatch':
- msg = "More than one %s exists with the name '%s'." % \
- (manager.resource_class.__name__.lower(), name_or_id)
- raise exceptions.CommandError(msg)
- else:
- pass
-
- for resource in manager.list():
- # short circuit and return the first match
- if (resource.get('id') == name_or_id or
- resource.get('name') == name_or_id):
- return resource
- else:
- # we found no match, report back this error:
- msg = "Could not find resource %s" % name_or_id
- raise exceptions.CommandError(msg)
-
-
-def format_dict(data):
- """Return a formatted string of key value pairs
-
- :param data: a dict
- :rtype: a string formatted to key='value'
- """
-
- output = ""
- for s in sorted(data):
- output = output + s + "='" + six.text_type(data[s]) + "', "
- return output[:-2]
-
-
-def format_list(data, separator=', '):
- """Return a formatted strings
-
- :param data: a list of strings
- :param separator: the separator to use between strings (default: ', ')
- :rtype: a string formatted based on separator
- """
-
- return separator.join(sorted(data))
-
-
-def format_list_of_dicts(data):
- """Return a formatted string of key value pairs for each dict
-
- :param data: a list of dicts
- :rtype: a string formatted to key='value' with dicts separated by new line
- """
-
- return '\n'.join(format_dict(i) for i in data)
-
-
-def get_field(item, field):
- try:
- if isinstance(item, dict):
- return item[field]
- else:
- return getattr(item, field)
- except Exception:
- msg = "Resource doesn't have field %s" % field
- raise exceptions.CommandError(msg)
-
-
-def get_item_properties(item, fields, mixed_case_fields=None, formatters=None):
- """Return a tuple containing the item properties.
-
- :param item: a single item resource (e.g. Server, Project, etc)
- :param fields: tuple of strings with the desired field names
- :param mixed_case_fields: tuple of field names to preserve case
- :param formatters: dictionary mapping field names to callables
- to format the values
- """
- if mixed_case_fields is None:
- mixed_case_fields = []
- if formatters is None:
- formatters = {}
-
- row = []
-
- for field in fields:
- if field in mixed_case_fields:
- field_name = field.replace(' ', '_')
- else:
- field_name = field.lower().replace(' ', '_')
- data = getattr(item, field_name, '')
- if field in formatters:
- row.append(formatters[field](data))
- else:
- row.append(data)
- return tuple(row)
-
-
-def get_dict_properties(item, fields, mixed_case_fields=None, formatters=None):
- """Return a tuple containing the item properties.
-
- :param item: a single dict resource
- :param fields: tuple of strings with the desired field names
- :param mixed_case_fields: tuple of field names to preserve case
- :param formatters: dictionary mapping field names to callables
- to format the values
- """
- if mixed_case_fields is None:
- mixed_case_fields = []
- if formatters is None:
- formatters = {}
-
- row = []
-
- for field in fields:
- if field in mixed_case_fields:
- field_name = field.replace(' ', '_')
- else:
- field_name = field.lower().replace(' ', '_')
- data = item[field_name] if field_name in item else ''
- if field in formatters:
- row.append(formatters[field](data))
- else:
- row.append(data)
- return tuple(row)
-
-
-def sort_items(items, sort_str):
- """Sort items based on sort keys and sort directions given by sort_str.
-
- :param items: a list or generator object of items
- :param sort_str: a string defining the sort rules, the format is
- '<key1>:[direction1],<key2>:[direction2]...', direction can be 'asc'
- for ascending or 'desc' for descending, if direction is not given,
- it's ascending by default
- :return: sorted items
- """
- if not sort_str:
- return items
- # items may be a generator object, transform it to a list
- items = list(items)
- sort_keys = sort_str.strip().split(',')
- for sort_key in reversed(sort_keys):
- reverse = False
- if ':' in sort_key:
- sort_key, direction = sort_key.split(':', 1)
- if not sort_key:
- msg = "empty string is not a valid sort key"
- raise exceptions.CommandError(msg)
- if direction not in ['asc', 'desc']:
- if not direction:
- direction = "empty string"
- msg = ("%s is not a valid sort direction for sort key %s, "
- "use asc or desc instead" % (direction, sort_key))
- raise exceptions.CommandError(msg)
- if direction == 'desc':
- reverse = True
- items.sort(key=lambda item: get_field(item, sort_key),
- reverse=reverse)
- return items
-
-
-def env(*vars, **kwargs):
- """Search for the first defined of possibly many env vars
-
- Returns the first environment variable defined in vars, or
- returns the default defined in kwargs.
- """
- for v in vars:
- value = os.environ.get(v, None)
- if value:
- return value
- return kwargs.get('default', '')
-
-
-def get_client_class(api_name, version, version_map):
- """Returns the client class for the requested API version
-
- :param api_name: the name of the API, e.g. 'compute', 'image', etc
- :param version: the requested API version
- :param version_map: a dict of client classes keyed by version
- :rtype: a client class for the requested API version
- """
- try:
- client_path = version_map[str(version)]
- except (KeyError, ValueError):
- msg = "Invalid %s client version '%s'. must be one of: %s" % (
- (api_name, version, ', '.join(list(version_map.keys()))))
- raise exceptions.UnsupportedVersion(msg)
-
- return importutils.import_class(client_path)
-
-
-def wait_for_status(status_f,
- res_id,
- status_field='status',
- success_status=['active'],
- error_status=['error'],
- sleep_time=5,
- callback=None):
- """Wait for status change on a resource during a long-running operation
-
- :param status_f: a status function that takes a single id argument
- :param res_id: the resource id to watch
- :param status_field: the status attribute in the returned resource object
- :param success_status: a list of status strings for successful completion
- :param error_status: a list of status strings for error
- :param sleep_time: wait this long (seconds)
- :param callback: called per sleep cycle, useful to display progress
- :rtype: True on success
- """
- while True:
- res = status_f(res_id)
- status = getattr(res, status_field, '').lower()
- if status in success_status:
- retval = True
- break
- elif status in error_status:
- retval = False
- break
- if callback:
- progress = getattr(res, 'progress', None) or 0
- callback(progress)
- time.sleep(sleep_time)
- return retval
-
-
-def wait_for_delete(manager,
- res_id,
- status_field='status',
- error_status=['error'],
- exception_name=['NotFound'],
- sleep_time=5,
- timeout=300,
- callback=None):
- """Wait for resource deletion
-
- :param manager: the manager from which we can get the resource
- :param res_id: the resource id to watch
- :param status_field: the status attribute in the returned resource object,
- this is used to check for error states while the resource is being
- deleted
- :param error_status: a list of status strings for error
- :param exception_name: a list of exception strings for deleted case
- :param sleep_time: wait this long between checks (seconds)
- :param timeout: check until this long (seconds)
- :param callback: called per sleep cycle, useful to display progress; this
- function is passed a progress value during each iteration of the wait
- loop
- :rtype: True on success, False if the resource has gone to error state or
- the timeout has been reached
- """
- total_time = 0
- while total_time < timeout:
- try:
- # might not be a bad idea to re-use find_resource here if it was
- # a bit more friendly in the exceptions it raised so we could just
- # handle a NotFound exception here without parsing the message
- res = manager.get(res_id)
- except Exception as ex:
- if type(ex).__name__ in exception_name:
- return True
- raise
-
- status = getattr(res, status_field, '').lower()
- if status in error_status:
- return False
-
- if callback:
- progress = getattr(res, 'progress', None) or 0
- callback(progress)
- time.sleep(sleep_time)
- total_time += sleep_time
-
- # if we got this far we've timed out
- return False
-
-
-def get_effective_log_level():
- """Returns the lowest logging level considered by logging handlers
-
- Retrieve and return the smallest log level set among the root
- logger's handlers (in case of multiple handlers).
- """
- root_log = logging.getLogger()
- min_log_lvl = logging.CRITICAL
- for handler in root_log.handlers:
- min_log_lvl = min(min_log_lvl, handler.level)
- return min_log_lvl
-
-
-def get_password(stdin, prompt=None, confirm=True):
- message = prompt or "User Password:"
- if hasattr(stdin, 'isatty') and stdin.isatty():
- try:
- while True:
- first_pass = getpass.getpass(message)
- if not confirm:
- return first_pass
- second_pass = getpass.getpass("Repeat " + message)
- if first_pass == second_pass:
- return first_pass
- print("The passwords entered were not the same")
- except EOFError: # Ctl-D
- raise exceptions.CommandError("Error reading password.")
- raise exceptions.CommandError("There was a request to be prompted for a"
- " password and a terminal was not detected.")
-
-
-def read_blob_file_contents(blob_file):
- try:
- with open(blob_file) as file:
- blob = file.read().strip()
- return blob
- except IOError:
- msg = "Error occurred trying to read from file %s"
- raise exceptions.CommandError(msg % blob_file)
+# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release
+# or Jun 2017.
+import sys
-def build_kwargs_dict(arg_name, value):
- """Return a dictionary containing `arg_name` if `value` is set."""
- kwargs = {}
- if value:
- kwargs[arg_name] = value
- return kwargs
+from osc_lib.utils import * # noqa
-def is_ascii(string):
- try:
- string.decode('ascii')
- return True
- except UnicodeDecodeError:
- return False
+sys.stderr.write(
+ "WARNING: %s is deprecated and will be removed after Jun 2017. "
+ "Please use osc_lib.utils\n" % __name__
+)
diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py
index 8e6eedc..4cc3be9 100644
--- a/openstackclient/compute/client.py
+++ b/openstackclient/compute/client.py
@@ -1,4 +1,4 @@
-# Copyright 2012-2013 OpenStack, LLC.
+# Copyright 2012-2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -15,10 +15,12 @@
import logging
-from openstackclient.common import exceptions
-from openstackclient.common import utils
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.i18n import _
+
LOG = logging.getLogger(__name__)
DEFAULT_API_VERSION = '2'
@@ -62,7 +64,7 @@ def make_client(instance):
if ext.name == "list_extensions"]
# Remember interface only if it is set
- kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface)
+ kwargs = utils.build_kwargs_dict('endpoint_type', instance.interface)
client = nova_client.Client(
version,
@@ -70,7 +72,7 @@ def make_client(instance):
extensions=extensions,
http_log_debug=http_log_debug,
timings=instance.timing,
- region_name=instance._region_name,
+ region_name=instance.region_name,
**kwargs
)
diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py
index 064fe5a..76c1b3b 100644
--- a/openstackclient/compute/v2/agent.py
+++ b/openstackclient/compute/v2/agent.py
@@ -15,16 +15,21 @@
"""Agent action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateAgent(command.ShowOne):
- """Create compute agent command"""
+ """Create compute agent"""
def get_parser(self, prog_name):
parser = super(CreateAgent, self).get_parser(prog_name)
@@ -96,19 +101,18 @@ class DeleteAgent(command.Command):
compute_client.agents.delete(id)
except Exception as e:
result += 1
- self.app.log.error(_("Failed to delete agent with "
- "ID '%(id)s': %(e)s")
- % {'id': id, 'e': e})
+ LOG.error(_("Failed to delete agent with ID '%(id)s': %(e)s"),
+ {'id': id, 'e': e})
if result > 0:
total = len(parsed_args.id)
msg = (_("%(result)s of %(total)s agents failed "
- "to delete.") % {'result': result, 'total': total})
+ "to delete.") % {'result': result, 'total': total})
raise exceptions.CommandError(msg)
class ListAgent(command.Lister):
- """List compute agent command"""
+ """List compute agents"""
def get_parser(self, prog_name):
parser = super(ListAgent, self).get_parser(prog_name)
@@ -138,7 +142,7 @@ class ListAgent(command.Lister):
class SetAgent(command.Command):
- """Set compute agent command"""
+ """Set compute agent properties"""
def get_parser(self, prog_name):
parser = super(SetAgent, self).get_parser(prog_name)
@@ -148,28 +152,48 @@ class SetAgent(command.Command):
help=_("ID of the agent")
)
parser.add_argument(
- "version",
+ "--agent-version",
+ dest="version",
metavar="<version>",
help=_("Version of the agent")
)
parser.add_argument(
- "url",
+ "--url",
metavar="<url>",
- help=_("URL")
+ help=_("URL of the agent")
)
parser.add_argument(
- "md5hash",
+ "--md5hash",
metavar="<md5hash>",
- help=_("MD5 hash")
+ help=_("MD5 hash of the agent")
)
return parser
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
+ data = compute_client.agents.list(hypervisor=None)
+ agent = {}
+
+ for s in data:
+ if s.agent_id == int(parsed_args.id):
+ agent['version'] = s.version
+ agent['url'] = s.url
+ agent['md5hash'] = s.md5hash
+ if agent == {}:
+ msg = _("No agent with a ID of '%(id)s' exists.")
+ raise exceptions.CommandError(msg % parsed_args.id)
+
+ if parsed_args.version:
+ agent['version'] = parsed_args.version
+ if parsed_args.url:
+ agent['url'] = parsed_args.url
+ if parsed_args.md5hash:
+ agent['md5hash'] = parsed_args.md5hash
+
args = (
parsed_args.id,
- parsed_args.version,
- parsed_args.url,
- parsed_args.md5hash
+ agent['version'],
+ agent['url'],
+ agent['md5hash'],
)
compute_client.agents.update(*args)
diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py
index 752e0fd..2e2838c 100644
--- a/openstackclient/compute/v2/aggregate.py
+++ b/openstackclient/compute/v2/aggregate.py
@@ -16,14 +16,20 @@
"""Compute v2 Aggregate action implementations"""
+import logging
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class AddAggregateHost(command.ShowOne):
"""Add host to aggregate"""
@@ -99,25 +105,37 @@ class CreateAggregate(command.ShowOne):
class DeleteAggregate(command.Command):
- """Delete an existing aggregate"""
+ """Delete existing aggregate(s)"""
def get_parser(self, prog_name):
parser = super(DeleteAggregate, self).get_parser(prog_name)
parser.add_argument(
'aggregate',
metavar='<aggregate>',
- help=_("Aggregate to delete (name or ID)")
+ nargs='+',
+ help=_("Aggregate(s) to delete (name or ID)")
)
return parser
def take_action(self, parsed_args):
-
compute_client = self.app.client_manager.compute
- data = utils.find_resource(
- compute_client.aggregates,
- parsed_args.aggregate,
- )
- compute_client.aggregates.delete(data.id)
+ result = 0
+ for a in parsed_args.aggregate:
+ try:
+ data = utils.find_resource(
+ compute_client.aggregates, a)
+ compute_client.aggregates.delete(data.id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete aggregate with name or "
+ "ID '%(aggregate)s': %(e)s")
+ % {'aggregate': a, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.aggregate)
+ msg = (_("%(result)s of %(total)s aggregates failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListAggregate(command.Lister):
diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py
index 1165862..02be99d 100644
--- a/openstackclient/compute/v2/console.py
+++ b/openstackclient/compute/v2/console.py
@@ -15,12 +15,13 @@
"""Compute v2 Console action implementations"""
-import six
import sys
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import utils
+import six
+
from openstackclient.i18n import _
@@ -86,15 +87,36 @@ class ShowConsoleURL(command.ShowOne):
dest='url_type',
action='store_const',
const='xvpvnc',
- help=_("Show xpvnc console URL")
+ help=_("Show xvpvnc console URL")
)
type_group.add_argument(
'--spice',
dest='url_type',
action='store_const',
- const='spice',
+ const='spice-html5',
help=_("Show SPICE console URL")
)
+ type_group.add_argument(
+ '--rdp',
+ dest='url_type',
+ action='store_const',
+ const='rdp-html5',
+ help=_("Show RDP console URL"),
+ )
+ type_group.add_argument(
+ '--serial',
+ dest='url_type',
+ action='store_const',
+ const='serial',
+ help=_("Show serial console URL"),
+ )
+ type_group.add_argument(
+ '--mks',
+ dest='url_type',
+ action='store_const',
+ const='webmks',
+ help=_("Show WebMKS console URL"),
+ )
return parser
def take_action(self, parsed_args):
@@ -104,14 +126,26 @@ class ShowConsoleURL(command.ShowOne):
parsed_args.server,
)
+ data = None
if parsed_args.url_type in ['novnc', 'xvpvnc']:
data = server.get_vnc_console(parsed_args.url_type)
- if parsed_args.url_type in ['spice']:
+ if parsed_args.url_type in ['spice-html5']:
data = server.get_spice_console(parsed_args.url_type)
+ if parsed_args.url_type in ['rdp-html5']:
+ data = server.get_rdp_console(parsed_args.url_type)
+ if parsed_args.url_type in ['serial']:
+ data = server.get_serial_console(parsed_args.url_type)
+ if parsed_args.url_type in ['webmks']:
+ data = server.get_mks_console()
if not data:
return ({}, {})
info = {}
- info.update(data['console'])
+ # NOTE(Rui Chen): Return 'remote_console' in compute microversion API
+ # 2.6 and later, return 'console' in compute
+ # microversion API from 2.0 to 2.5, do compatibility
+ # handle for different microversion API.
+ console_data = data.get('remote_console', data.get('console'))
+ info.update(console_data)
return zip(*sorted(six.iteritems(info)))
diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py
index daac97d..c14d29f 100644
--- a/openstackclient/compute/v2/fixedip.py
+++ b/openstackclient/compute/v2/fixedip.py
@@ -15,28 +15,43 @@
"""Fixed IP action implementations"""
-from openstackclient.common import command
-from openstackclient.common import utils
+import logging
+
+from osc_lib.command import command
+from osc_lib import utils
+
+from openstackclient.i18n import _
class AddFixedIP(command.Command):
"""Add fixed IP address to server"""
+ # TODO(tangchen): Remove this class and ``ip fixed add`` command
+ # two cycles after Mitaka.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
def get_parser(self, prog_name):
parser = super(AddFixedIP, self).get_parser(prog_name)
parser.add_argument(
"network",
metavar="<network>",
- help="Network to fetch an IP address from (name or ID)",
+ help=_("Network to fetch an IP address from (name or ID)"),
)
parser.add_argument(
"server",
metavar="<server>",
- help="Server to receive the IP address (name or ID)",
+ help=_("Server to receive the IP address (name or ID)"),
)
return parser
def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "server add fixed ip" instead.'))
+
compute_client = self.app.client_manager.compute
network = utils.find_resource(
@@ -51,21 +66,32 @@ class AddFixedIP(command.Command):
class RemoveFixedIP(command.Command):
"""Remove fixed IP address from server"""
+ # TODO(tangchen): Remove this class and ``ip fixed remove`` command
+ # two cycles after Mitaka.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
def get_parser(self, prog_name):
parser = super(RemoveFixedIP, self).get_parser(prog_name)
parser.add_argument(
"ip_address",
metavar="<ip-address>",
- help="IP address to remove from server (name only)",
+ help=_("IP address to remove from server (name only)"),
)
parser.add_argument(
"server",
metavar="<server>",
- help="Server to remove the IP address from (name or ID)",
+ help=_("Server to remove the IP address from (name or ID)"),
)
return parser
def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "server remove fixed ip" instead.'))
+
compute_client = self.app.client_manager.compute
server = utils.find_resource(
diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py
index 87909a1..b3f09ce 100644
--- a/openstackclient/compute/v2/flavor.py
+++ b/openstackclient/compute/v2/flavor.py
@@ -15,16 +15,21 @@
"""Flavor action implementations"""
+import logging
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+LOG = logging.getLogger(__name__)
+
+
def _find_flavor(compute_client, flavor):
try:
return compute_client.flavors.get(flavor)
@@ -116,10 +121,29 @@ class CreateFlavor(command.ShowOne):
action="store_false",
help=_("Flavor is not available to other projects")
)
+ parser.add_argument(
+ "--property",
+ metavar="<key=value>",
+ action=parseractions.KeyValueAction,
+ help=_("Property to add for this flavor "
+ "(repeat option to set multiple properties)")
+ )
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_("Allow <project> to access private flavor (name or ID) "
+ "(Must be used with --private option)"),
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
+ identity_client = self.app.client_manager.identity
+
+ if parsed_args.project and parsed_args.public:
+ msg = _("--project is only allowed with --private")
+ raise exceptions.CommandError(msg)
args = (
parsed_args.name,
@@ -133,28 +157,65 @@ class CreateFlavor(command.ShowOne):
parsed_args.public
)
- flavor = compute_client.flavors.create(*args)._info.copy()
- flavor.pop("links")
+ flavor = compute_client.flavors.create(*args)
- return zip(*sorted(six.iteritems(flavor)))
+ if parsed_args.project:
+ try:
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ compute_client.flavor_access.add_tenant_access(
+ parsed_args.id, project_id)
+ except Exception as e:
+ msg = _("Failed to add project %(project)s access to "
+ "flavor: %(e)s")
+ LOG.error(msg % {'project': parsed_args.project, 'e': e})
+ if parsed_args.property:
+ try:
+ flavor.set_keys(parsed_args.property)
+ except Exception as e:
+ LOG.error(_("Failed to set flavor property: %s"), e)
+
+ flavor_info = flavor._info.copy()
+ flavor_info.pop("links")
+ flavor_info['properties'] = utils.format_dict(flavor.get_keys())
+
+ return zip(*sorted(six.iteritems(flavor_info)))
class DeleteFlavor(command.Command):
- """Delete flavor"""
+ """Delete flavor(s)"""
def get_parser(self, prog_name):
parser = super(DeleteFlavor, self).get_parser(prog_name)
parser.add_argument(
"flavor",
metavar="<flavor>",
- help=_("Flavor to delete (name or ID)")
+ nargs='+',
+ help=_("Flavor(s) to delete (name or ID)")
)
return parser
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
- flavor = _find_flavor(compute_client, parsed_args.flavor)
- compute_client.flavors.delete(flavor.id)
+ result = 0
+ for f in parsed_args.flavor:
+ try:
+ flavor = _find_flavor(compute_client, f)
+ compute_client.flavors.delete(flavor.id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete flavor with name or "
+ "ID '%(flavor)s': %(e)s")
+ % {'flavor': f, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.flavor)
+ msg = (_("%(result)s of %(total)s flavors failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListFlavor(command.Lister):
@@ -274,16 +335,12 @@ class SetFlavor(command.Command):
flavor = _find_flavor(compute_client, parsed_args.flavor)
- if not parsed_args.property and not parsed_args.project:
- raise exceptions.CommandError(_("Nothing specified to be set."))
-
result = 0
if parsed_args.property:
try:
flavor.set_keys(parsed_args.property)
except Exception as e:
- self.app.log.error(
- _("Failed to set flavor property: %s") % str(e))
+ LOG.error(_("Failed to set flavor property: %s"), e)
result += 1
if parsed_args.project:
@@ -300,13 +357,12 @@ class SetFlavor(command.Command):
compute_client.flavor_access.add_tenant_access(
flavor.id, project_id)
except Exception as e:
- self.app.log.error(_("Failed to set flavor access to"
- " project: %s") % str(e))
+ LOG.error(_("Failed to set flavor access to project: %s"), e)
result += 1
if result > 0:
raise exceptions.CommandError(_("Command Failed: One or more of"
- " the operations failed"))
+ " the operations failed"))
class ShowFlavor(command.ShowOne):
@@ -324,7 +380,27 @@ class ShowFlavor(command.ShowOne):
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
resource_flavor = _find_flavor(compute_client, parsed_args.flavor)
+
+ access_projects = None
+ # get access projects list of this flavor
+ if not resource_flavor.is_public:
+ try:
+ flavor_access = compute_client.flavor_access.list(
+ flavor=resource_flavor.id)
+ projects = [utils.get_field(access, 'tenant_id')
+ for access in flavor_access]
+ # TODO(Huanxuan Ao): This format case can be removed after
+ # patch https://review.openstack.org/#/c/330223/ merged.
+ access_projects = utils.format_list(projects)
+ except Exception as e:
+ msg = _("Failed to get access projects list "
+ "for flavor '%(flavor)s': %(e)s")
+ LOG.error(msg % {'flavor': parsed_args.flavor, 'e': e})
+
flavor = resource_flavor._info.copy()
+ flavor.update({
+ 'access_project_ids': access_projects
+ })
flavor.pop("links", None)
flavor['properties'] = utils.format_dict(resource_flavor.get_keys())
@@ -365,16 +441,12 @@ class UnsetFlavor(command.Command):
flavor = _find_flavor(compute_client, parsed_args.flavor)
- if not parsed_args.property and not parsed_args.project:
- raise exceptions.CommandError(_("Nothing specified to be unset."))
-
result = 0
if parsed_args.property:
try:
flavor.unset_keys(parsed_args.property)
except Exception as e:
- self.app.log.error(
- _("Failed to unset flavor property: %s") % str(e))
+ LOG.error(_("Failed to unset flavor property: %s"), e)
result += 1
if parsed_args.project:
@@ -391,10 +463,10 @@ class UnsetFlavor(command.Command):
compute_client.flavor_access.remove_tenant_access(
flavor.id, project_id)
except Exception as e:
- self.app.log.error(_("Failed to remove flavor access from"
- " project: %s") % str(e))
+ LOG.error(_("Failed to remove flavor access from project: %s"),
+ e)
result += 1
if result > 0:
raise exceptions.CommandError(_("Command Failed: One or more of"
- " the operations failed"))
+ " the operations failed"))
diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py
index fac4d2e..8398ea5 100644
--- a/openstackclient/compute/v2/floatingip.py
+++ b/openstackclient/compute/v2/floatingip.py
@@ -15,28 +15,43 @@
"""Floating IP action implementations"""
-from openstackclient.common import command
-from openstackclient.common import utils
+import logging
+
+from osc_lib.command import command
+from osc_lib import utils
+
+from openstackclient.i18n import _
class AddFloatingIP(command.Command):
"""Add floating IP address to server"""
+ # TODO(tangchen): Remove this class and ``ip floating add`` command
+ # two cycles after Mitaka.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
def get_parser(self, prog_name):
parser = super(AddFloatingIP, self).get_parser(prog_name)
parser.add_argument(
"ip_address",
metavar="<ip-address>",
- help="IP address to add to server (name only)",
+ help=_("IP address to add to server (name only)"),
)
parser.add_argument(
"server",
metavar="<server>",
- help="Server to receive the IP address (name or ID)",
+ help=_("Server to receive the IP address (name or ID)"),
)
return parser
def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "server add floating ip" instead.'))
+
compute_client = self.app.client_manager.compute
server = utils.find_resource(
@@ -48,21 +63,32 @@ class AddFloatingIP(command.Command):
class RemoveFloatingIP(command.Command):
"""Remove floating IP address from server"""
+ # TODO(tangchen): Remove this class and ``ip floating remove`` command
+ # two cycles after Mitaka.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
def get_parser(self, prog_name):
parser = super(RemoveFloatingIP, self).get_parser(prog_name)
parser.add_argument(
"ip_address",
metavar="<ip-address>",
- help="IP address to remove from server (name only)",
+ help=_("IP address to remove from server (name only)"),
)
parser.add_argument(
"server",
metavar="<server>",
- help="Server to remove the IP address from (name or ID)",
+ help=_("Server to remove the IP address from (name or ID)"),
)
return parser
def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "server remove floating ip" instead.'))
+
compute_client = self.app.client_manager.compute
server = utils.find_resource(
diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py
deleted file mode 100644
index 997e032..0000000
--- a/openstackclient/compute/v2/floatingippool.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-#
-
-"""Floating IP Pool action implementations"""
-
-from openstackclient.common import command
-from openstackclient.common import utils
-
-
-class ListFloatingIPPool(command.Lister):
- """List pools of floating IP addresses"""
-
- def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
-
- columns = ('Name',)
-
- data = compute_client.floating_ip_pools.list()
-
- return (columns,
- (utils.get_item_properties(
- s, columns,
- formatters={},
- ) for s in data))
diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py
index 73e2cdf..4785377 100644
--- a/openstackclient/compute/v2/host.py
+++ b/openstackclient/compute/v2/host.py
@@ -1,4 +1,4 @@
-# Copyright 2013 OpenStack, LLC.
+# Copyright 2012-2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -15,13 +15,14 @@
"""Host action implementations"""
-from openstackclient.common import command
-from openstackclient.common import utils
+from osc_lib.command import command
+from osc_lib import utils
+
from openstackclient.i18n import _
class ListHost(command.Lister):
- """List host command"""
+ """List hosts"""
def get_parser(self, prog_name):
parser = super(ListHost, self).get_parser(prog_name)
@@ -53,7 +54,7 @@ class SetHost(command.Command):
parser.add_argument(
"host",
metavar="<host>",
- help=_("The host to modify (name or ID)")
+ help=_("Host to modify (name only)")
)
status = parser.add_mutually_exclusive_group()
status.add_argument(
@@ -83,28 +84,30 @@ class SetHost(command.Command):
kwargs = {}
if parsed_args.enable:
- kwargs['status'] = True
+ kwargs['status'] = 'enable'
if parsed_args.disable:
- kwargs['status'] = False
+ kwargs['status'] = 'disable'
if parsed_args.enable_maintenance:
- kwargs['maintenance_mode'] = True
+ kwargs['maintenance_mode'] = 'enable'
if parsed_args.disable_maintenance:
- kwargs['maintenance_mode'] = False
+ kwargs['maintenance_mode'] = 'disable'
compute_client = self.app.client_manager.compute
- foundhost = utils.find_resource(
- compute_client.hosts,
- parsed_args.host
- )
+
+ # More than one hosts will be returned by using find_resource()
+ # so that the return value cannot be used in host update() method.
+ # find_resource() is just used for checking existence of host and
+ # keeping the exception message consistent with other commands.
+ utils.find_resource(compute_client.hosts, parsed_args.host)
compute_client.hosts.update(
- foundhost.id,
+ parsed_args.host,
kwargs
)
class ShowHost(command.Lister):
- """Show host command"""
+ """Display host details"""
def get_parser(self, prog_name):
parser = super(ShowHost, self).get_parser(prog_name)
diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py
index 333a7de..0062505 100644
--- a/openstackclient/compute/v2/hypervisor.py
+++ b/openstackclient/compute/v2/hypervisor.py
@@ -1,4 +1,4 @@
-# Copyright 2013 OpenStack, LLC.
+# Copyright 2012-2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -16,10 +16,11 @@
"""Hypervisor action implementations"""
import re
+
+from osc_lib.command import command
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
diff --git a/openstackclient/compute/v2/hypervisor_stats.py b/openstackclient/compute/v2/hypervisor_stats.py
index 290f541..c6fd299 100644
--- a/openstackclient/compute/v2/hypervisor_stats.py
+++ b/openstackclient/compute/v2/hypervisor_stats.py
@@ -14,10 +14,9 @@
"""Hypervisor Stats action implementations"""
+from osc_lib.command import command
import six
-from openstackclient.common import command
-
class ShowHypervisorStats(command.ShowOne):
"""Display hypervisor stats details"""
diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py
index 8af209f..d30fd42 100644
--- a/openstackclient/compute/v2/keypair.py
+++ b/openstackclient/compute/v2/keypair.py
@@ -16,16 +16,21 @@
"""Keypair action implementations"""
import io
+import logging
import os
-import six
import sys
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import utils
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+import six
+
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateKeypair(command.ShowOne):
"""Create new public key"""
@@ -77,20 +82,37 @@ class CreateKeypair(command.ShowOne):
class DeleteKeypair(command.Command):
- """Delete public key"""
+ """Delete public key(s)"""
def get_parser(self, prog_name):
parser = super(DeleteKeypair, self).get_parser(prog_name)
parser.add_argument(
'name',
metavar='<key>',
- help=_("Public key to delete (name only)")
+ nargs='+',
+ help=_("Public key(s) to delete (name only)")
)
return parser
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
- compute_client.keypairs.delete(parsed_args.name)
+ result = 0
+ for n in parsed_args.name:
+ try:
+ data = utils.find_resource(
+ compute_client.keypairs, n)
+ compute_client.keypairs.delete(data.name)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete public key with name "
+ "'%(name)s': %(e)s")
+ % {'name': n, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.name)
+ msg = (_("%(result)s of %(total)s public keys failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListKeypair(command.Lister):
@@ -124,7 +146,7 @@ class ShowKeypair(command.ShowOne):
'--public-key',
action='store_true',
default=False,
- help=_("Show only bare public key")
+ help=_("Show only bare public key (name only)")
)
return parser
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index 5688b55..3e6903b 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -18,24 +18,28 @@
import argparse
import getpass
import io
+import logging
import os
-import six
import sys
-from openstackclient.common import command
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+import six
try:
from novaclient.v2 import servers
except ImportError:
from novaclient.v1_1 import servers
-from openstackclient.common import exceptions
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+LOG = logging.getLogger(__name__)
+
+
def _format_servers_list_networks(networks):
"""Return a formatted string of a server's networks
@@ -164,29 +168,69 @@ def _prep_server_detail(compute_client, server):
return info
-def _prep_image_detail(image_client, image_id):
- """Prepare the detailed image dict for printing
-
- :param image_client: an image client instance
- :param image_id: id of image created
- :rtype: a dict of image details
- """
-
- info = utils.find_resource(
- image_client.images,
- image_id,
- )
- # Glance client V2 doesn't have _info attribute
- # The following condition deals with it.
- return getattr(info, "_info", info)
-
-
def _show_progress(progress):
if progress:
sys.stdout.write('\rProgress: %s' % progress)
sys.stdout.flush()
+class AddFixedIP(command.Command):
+ """Add fixed IP address to server"""
+
+ def get_parser(self, prog_name):
+ parser = super(AddFixedIP, self).get_parser(prog_name)
+ parser.add_argument(
+ "server",
+ metavar="<server>",
+ help=_("Server (name or ID) to receive the fixed IP address"),
+ )
+ parser.add_argument(
+ "network",
+ metavar="<network>",
+ help=_("Network (name or ID) to allocate "
+ "the fixed IP address from"),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+
+ server = utils.find_resource(
+ compute_client.servers, parsed_args.server)
+
+ network = utils.find_resource(
+ compute_client.networks, parsed_args.network)
+
+ server.add_fixed_ip(network.id)
+
+
+class AddFloatingIP(command.Command):
+ """Add floating IP address to server"""
+
+ def get_parser(self, prog_name):
+ parser = super(AddFloatingIP, self).get_parser(prog_name)
+ parser.add_argument(
+ "server",
+ metavar="<server>",
+ help=_("Server (name or ID) to receive the floating IP address"),
+ )
+ parser.add_argument(
+ "ip_address",
+ metavar="<ip-address>",
+ help=_("Floating IP address (IP address only) to assign "
+ "to server"),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+
+ server = utils.find_resource(
+ compute_client.servers, parsed_args.server)
+
+ server.add_floating_ip(parsed_args.ip_address)
+
+
class AddServerSecurityGroup(command.Command):
"""Add security group to server"""
@@ -216,7 +260,7 @@ class AddServerSecurityGroup(command.Command):
parsed_args.group,
)
- server.add_security_group(security_group.name)
+ server.add_security_group(security_group.id)
class AddServerVolume(command.Command):
@@ -538,8 +582,8 @@ class CreateServer(command.ShowOne):
scheduler_hints=hints,
config_drive=config_drive)
- self.log.debug('boot_args: %s', boot_args)
- self.log.debug('boot_kwargs: %s', boot_kwargs)
+ LOG.debug('boot_args: %s', boot_args)
+ LOG.debug('boot_kwargs: %s', boot_kwargs)
# Wrap the call to catch exceptions in order to close files
try:
@@ -560,8 +604,8 @@ class CreateServer(command.ShowOne):
):
sys.stdout.write('\n')
else:
- self.log.error(_('Error creating server: %s'),
- parsed_args.server_name)
+ LOG.error(_('Error creating server: %s'),
+ parsed_args.server_name)
sys.stdout.write(_('Error creating server\n'))
raise SystemExit
@@ -597,63 +641,6 @@ class CreateServerDump(command.Command):
).trigger_crash_dump()
-class CreateServerImage(command.ShowOne):
- """Create a new disk image from a running server"""
-
- def get_parser(self, prog_name):
- parser = super(CreateServerImage, self).get_parser(prog_name)
- parser.add_argument(
- 'server',
- metavar='<server>',
- help=_('Server (name or ID)'),
- )
- parser.add_argument(
- '--name',
- metavar='<image-name>',
- help=_('Name of new image (default is server name)'),
- )
- parser.add_argument(
- '--wait',
- action='store_true',
- help=_('Wait for image create to complete'),
- )
- return parser
-
- def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
- image_client = self.app.client_manager.image
- server = utils.find_resource(
- compute_client.servers,
- parsed_args.server,
- )
- if parsed_args.name:
- name = parsed_args.name
- else:
- name = server.name
-
- image_id = compute_client.servers.create_image(
- server,
- name,
- )
-
- if parsed_args.wait:
- if utils.wait_for_status(
- image_client.images.get,
- image_id,
- callback=_show_progress,
- ):
- sys.stdout.write('\n')
- else:
- self.log.error(_('Error creating snapshot of server: %s'),
- parsed_args.server)
- sys.stdout.write(_('Error creating server snapshot\n'))
- raise SystemExit
-
- image = _prep_image_detail(image_client, image_id)
-
- return zip(*sorted(six.iteritems(image)))
-
-
class DeleteServer(command.Command):
"""Delete server(s)"""
@@ -686,8 +673,8 @@ class DeleteServer(command.Command):
):
sys.stdout.write('\n')
else:
- self.log.error(_('Error deleting server: %s'),
- server_obj.id)
+ LOG.error(_('Error deleting server: %s'),
+ server_obj.id)
sys.stdout.write(_('Error deleting server\n'))
raise SystemExit
@@ -836,7 +823,7 @@ class ListServer(command.Lister):
'all_tenants': parsed_args.all_projects,
'user_id': user_id,
}
- self.log.debug('search options: %s', search_opts)
+ LOG.debug('search options: %s', search_opts)
if parsed_args.long:
columns = (
@@ -846,6 +833,8 @@ class ListServer(command.Lister):
'OS-EXT-STS:task_state',
'OS-EXT-STS:power_state',
'Networks',
+ 'Image Name',
+ 'Image ID',
'OS-EXT-AZ:availability_zone',
'OS-EXT-SRV-ATTR:host',
'Metadata',
@@ -857,6 +846,8 @@ class ListServer(command.Lister):
'Task State',
'Power State',
'Networks',
+ 'Image Name',
+ 'Image ID',
'Availability Zone',
'Host',
'Properties',
@@ -873,12 +864,14 @@ class ListServer(command.Lister):
'Name',
'Status',
'Networks',
+ 'Image Name',
)
column_headers = (
'ID',
'Name',
'Status',
'Networks',
+ 'Image Name',
)
mixed_case_fields = []
@@ -890,17 +883,42 @@ class ListServer(command.Lister):
data = compute_client.servers.list(search_opts=search_opts,
marker=marker_id,
limit=parsed_args.limit)
- return (column_headers,
- (utils.get_item_properties(
- s, columns,
- mixed_case_fields=mixed_case_fields,
- formatters={
- 'OS-EXT-STS:power_state':
- _format_servers_list_power_state,
- 'Networks': _format_servers_list_networks,
- 'Metadata': utils.format_dict,
- },
- ) for s in data))
+
+ images = {}
+ # Create a dict that maps image_id to image object.
+ # Needed so that we can display the "Image Name" column.
+ # "Image Name" is not crucial, so we swallow any exceptions.
+ try:
+ images_list = self.app.client_manager.image.images.list()
+ for i in images_list:
+ images[i.id] = i
+ except Exception:
+ pass
+
+ # Populate image_name and image_id attributes of server objects
+ # so that we can display "Image Name" and "Image ID" columns.
+ for s in data:
+ if 'id' in s.image:
+ image = images.get(s.image['id'])
+ if image:
+ s.image_name = image.name
+ s.image_id = s.image['id']
+ else:
+ s.image_name = ''
+ s.image_id = ''
+
+ table = (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ mixed_case_fields=mixed_case_fields,
+ formatters={
+ 'OS-EXT-STS:power_state':
+ _format_servers_list_power_state,
+ 'Networks': _format_servers_list_networks,
+ 'Metadata': utils.format_dict,
+ },
+ ) for s in data))
+ return table
class LockServer(command.Command):
@@ -1013,8 +1031,8 @@ class MigrateServer(command.Command):
):
sys.stdout.write(_('Complete\n'))
else:
- self.log.error(_('Error migrating server: %s'),
- server.id)
+ LOG.error(_('Error migrating server: %s'),
+ server.id)
sys.stdout.write(_('Error migrating server\n'))
raise SystemExit
@@ -1089,8 +1107,8 @@ class RebootServer(command.Command):
):
sys.stdout.write(_('Complete\n'))
else:
- self.log.error(_('Error rebooting server: %s'),
- server.id)
+ LOG.error(_('Error rebooting server: %s'),
+ server.id)
sys.stdout.write(_('Error rebooting server\n'))
raise SystemExit
@@ -1142,8 +1160,8 @@ class RebuildServer(command.ShowOne):
):
sys.stdout.write(_('Complete\n'))
else:
- self.log.error(_('Error rebuilding server: %s'),
- server.id)
+ LOG.error(_('Error rebuilding server: %s'),
+ server.id)
sys.stdout.write(_('Error rebuilding server\n'))
raise SystemExit
@@ -1151,6 +1169,61 @@ class RebuildServer(command.ShowOne):
return zip(*sorted(six.iteritems(details)))
+class RemoveFixedIP(command.Command):
+ """Remove fixed IP address from server"""
+
+ def get_parser(self, prog_name):
+ parser = super(RemoveFixedIP, self).get_parser(prog_name)
+ parser.add_argument(
+ "server",
+ metavar="<server>",
+ help=_("Server (name or ID) to remove the fixed IP address from"),
+ )
+ parser.add_argument(
+ "ip_address",
+ metavar="<ip-address>",
+ help=_("Fixed IP address (IP address only) to remove from the "
+ "server"),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+
+ server = utils.find_resource(
+ compute_client.servers, parsed_args.server)
+
+ server.remove_fixed_ip(parsed_args.ip_address)
+
+
+class RemoveFloatingIP(command.Command):
+ """Remove floating IP address from server"""
+
+ def get_parser(self, prog_name):
+ parser = super(RemoveFloatingIP, self).get_parser(prog_name)
+ parser.add_argument(
+ "server",
+ metavar="<server>",
+ help=_("Server (name or ID) to remove the "
+ "floating IP address from"),
+ )
+ parser.add_argument(
+ "ip_address",
+ metavar="<ip-address>",
+ help=_("Floating IP address (IP address only) "
+ "to remove from server"),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+
+ server = utils.find_resource(
+ compute_client.servers, parsed_args.server)
+
+ server.remove_floating_ip(parsed_args.ip_address)
+
+
class RemoveServerSecurityGroup(command.Command):
"""Remove security group from server"""
@@ -1180,7 +1253,7 @@ class RemoveServerSecurityGroup(command.Command):
parsed_args.group,
)
- server.remove_security_group(security_group)
+ server.remove_security_group(security_group.id)
class RemoveServerVolume(command.Command):
@@ -1296,8 +1369,8 @@ class ResizeServer(command.Command):
):
sys.stdout.write(_('Complete\n'))
else:
- self.log.error(_('Error resizing server: %s'),
- server.id)
+ LOG.error(_('Error resizing server: %s'),
+ server.id)
sys.stdout.write(_('Error resizing server\n'))
raise SystemExit
elif parsed_args.confirm:
@@ -1612,7 +1685,7 @@ class SshServer(command.Command):
ip_address = _get_ip_address(server.addresses,
parsed_args.address_type,
ip_address_family)
- self.log.debug("ssh command: %s", (cmd % (login, ip_address)))
+ LOG.debug("ssh command: %s", (cmd % (login, ip_address)))
os.system(cmd % (login, ip_address))
diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py
index 24d7101..c0e2e5e 100644
--- a/openstackclient/compute/v2/server_backup.py
+++ b/openstackclient/compute/v2/server_backup.py
@@ -17,12 +17,12 @@
import sys
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
from oslo_utils import importutils
import six
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import utils
from openstackclient.i18n import _
diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py
index 2e275b7..d51b1ec 100644
--- a/openstackclient/compute/v2/server_group.py
+++ b/openstackclient/compute/v2/server_group.py
@@ -15,12 +15,18 @@
"""Compute v2 Server Group action implementations"""
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import utils
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
_formatters = {
'policies': utils.format_list,
'members': utils.format_list,
@@ -94,7 +100,7 @@ class DeleteServerGroup(command.Command):
# Catch all exceptions in order to avoid to block the next deleting
except Exception as e:
result += 1
- self.app.log.error(e)
+ LOG.error(e)
if result > 0:
total = len(parsed_args.server_group)
diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py
new file mode 100644
index 0000000..285c7fd
--- /dev/null
+++ b/openstackclient/compute/v2/server_image.py
@@ -0,0 +1,113 @@
+# Copyright 2012-2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Compute v2 Server action implementations"""
+
+import logging
+import sys
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+from oslo_utils import importutils
+import six
+
+from openstackclient.i18n import _
+
+
+LOG = logging.getLogger(__name__)
+
+
+def _show_progress(progress):
+ if progress:
+ sys.stdout.write('\rProgress: %s' % progress)
+ sys.stdout.flush()
+
+
+class CreateServerImage(command.ShowOne):
+ """Create a new server disk image from an existing server"""
+
+ IMAGE_API_VERSIONS = {
+ "1": "openstackclient.image.v1.image",
+ "2": "openstackclient.image.v2.image",
+ }
+
+ def get_parser(self, prog_name):
+ parser = super(CreateServerImage, self).get_parser(prog_name)
+ parser.add_argument(
+ 'server',
+ metavar='<server>',
+ help=_('Server to create image (name or ID)'),
+ )
+ parser.add_argument(
+ '--name',
+ metavar='<image-name>',
+ help=_('Name of new disk image (default: server name)'),
+ )
+ parser.add_argument(
+ '--wait',
+ action='store_true',
+ help=_('Wait for operation to complete'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+
+ server = utils.find_resource(
+ compute_client.servers,
+ parsed_args.server,
+ )
+ if parsed_args.name:
+ image_name = parsed_args.name
+ else:
+ image_name = server.name
+
+ image_id = compute_client.servers.create_image(
+ server.id,
+ image_name,
+ )
+
+ image_client = self.app.client_manager.image
+ image = utils.find_resource(
+ image_client.images,
+ image_id,
+ )
+
+ if parsed_args.wait:
+ if utils.wait_for_status(
+ image_client.images.get,
+ image_id,
+ callback=_show_progress,
+ ):
+ sys.stdout.write('\n')
+ else:
+ LOG.error(_('Error creating server image: %s'),
+ parsed_args.server)
+ raise exceptions.CommandError
+
+ if self.app.client_manager._api_version['image'] == '1':
+ info = {}
+ info.update(image._info)
+ info['properties'] = utils.format_dict(info.get('properties', {}))
+ else:
+ # Get the right image module to format the output
+ image_module = importutils.import_module(
+ self.IMAGE_API_VERSIONS[
+ self.app.client_manager._api_version['image']
+ ]
+ )
+ info = image_module._format_image(image)
+ return zip(*sorted(six.iteritems(info)))
diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py
index e7966a8..53624f5 100644
--- a/openstackclient/compute/v2/service.py
+++ b/openstackclient/compute/v2/service.py
@@ -1,4 +1,4 @@
-# Copyright 2013 OpenStack, LLC.
+# Copyright 2012-2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -15,32 +15,53 @@
"""Service action implementations"""
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import utils
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.i18n import _
+from openstackclient.i18n import _LE
+
+
+LOG = logging.getLogger(__name__)
class DeleteService(command.Command):
- """Delete service command"""
+ """Delete compute service(s)"""
def get_parser(self, prog_name):
parser = super(DeleteService, self).get_parser(prog_name)
parser.add_argument(
"service",
metavar="<service>",
- help=_("Compute service to delete (ID only)")
+ nargs='+',
+ help=_("Compute service(s) to delete (ID only)")
)
return parser
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
-
- compute_client.services.delete(parsed_args.service)
+ result = 0
+ for s in parsed_args.service:
+ try:
+ compute_client.services.delete(s)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete compute service with "
+ "ID '%(service)s': %(e)s")
+ % {'service': s, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.service)
+ msg = (_("%(result)s of %(total)s compute services failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListService(command.Lister):
- """List service command"""
+ """List compute services"""
def get_parser(self, prog_name):
parser = super(ListService, self).get_parser(prog_name)
@@ -66,7 +87,7 @@ class ListService(command.Lister):
compute_client = self.app.client_manager.compute
if parsed_args.long:
columns = (
- "Id",
+ "ID",
"Binary",
"Host",
"Zone",
@@ -77,7 +98,7 @@ class ListService(command.Lister):
)
else:
columns = (
- "Id",
+ "ID",
"Binary",
"Host",
"Zone",
@@ -94,7 +115,7 @@ class ListService(command.Lister):
class SetService(command.Command):
- """Set service command"""
+ """Set compute service properties"""
def get_parser(self, prog_name):
parser = super(SetService, self).get_parser(prog_name)
@@ -106,7 +127,7 @@ class SetService(command.Command):
parser.add_argument(
"service",
metavar="<service>",
- help=_("Name of service")
+ help=_("Name of service (Binary name)")
)
enabled_group = parser.add_mutually_exclusive_group()
enabled_group.add_argument(
@@ -126,6 +147,17 @@ class SetService(command.Command):
help=_("Reason for disabling the service (in quotas). "
"Should be used with --disable option.")
)
+ up_down_group = parser.add_mutually_exclusive_group()
+ up_down_group.add_argument(
+ '--up',
+ action='store_true',
+ help=_('Force up service'),
+ )
+ up_down_group.add_argument(
+ '--down',
+ action='store_true',
+ help=_('Force down service'),
+ )
return parser
def take_action(self, parsed_args):
@@ -138,20 +170,45 @@ class SetService(command.Command):
"--disable specified.")
raise exceptions.CommandError(msg)
+ result = 0
enabled = None
- if parsed_args.enable:
- enabled = True
- if parsed_args.disable:
- enabled = False
-
- if enabled is None:
- return
- elif enabled:
- cs.enable(parsed_args.host, parsed_args.service)
- else:
- if parsed_args.disable_reason:
- cs.disable_log_reason(parsed_args.host,
- parsed_args.service,
- parsed_args.disable_reason)
- else:
- cs.disable(parsed_args.host, parsed_args.service)
+ try:
+ if parsed_args.enable:
+ enabled = True
+ if parsed_args.disable:
+ enabled = False
+
+ if enabled is not None:
+ if enabled:
+ cs.enable(parsed_args.host, parsed_args.service)
+ else:
+ if parsed_args.disable_reason:
+ cs.disable_log_reason(parsed_args.host,
+ parsed_args.service,
+ parsed_args.disable_reason)
+ else:
+ cs.disable(parsed_args.host, parsed_args.service)
+ except Exception:
+ status = "enabled" if enabled else "disabled"
+ LOG.error(_LE("Failed to set service status to %s"), status)
+ result += 1
+
+ force_down = None
+ try:
+ if parsed_args.down:
+ force_down = True
+ if parsed_args.up:
+ force_down = False
+ if force_down is not None:
+ cs.force_down(parsed_args.host, parsed_args.service,
+ force_down=force_down)
+ except Exception:
+ state = "down" if force_down else "up"
+ LOG.error(_LE("Failed to set service state to %s"), state)
+ result += 1
+
+ if result > 0:
+ msg = _("Compute service %(service)s of host %(host)s failed to "
+ "set.") % {"service": parsed_args.service,
+ "host": parsed_args.host}
+ raise exceptions.CommandError(msg)
diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py
index b83bef1..2f35b01 100644
--- a/openstackclient/compute/v2/usage.py
+++ b/openstackclient/compute/v2/usage.py
@@ -18,10 +18,10 @@
import datetime
import sys
+from osc_lib.command import command
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py
index c166e66..0292aac 100644
--- a/openstackclient/identity/client.py
+++ b/openstackclient/identity/client.py
@@ -16,8 +16,10 @@
import logging
from keystoneclient.v2_0 import client as identity_client_v2
-from openstackclient.api import auth
-from openstackclient.common import utils
+from osc_lib import utils
+
+from openstackclient.i18n import _
+
LOG = logging.getLogger(__name__)
@@ -47,11 +49,11 @@ def make_client(instance):
LOG.debug('Instantiating identity client: %s', identity_client)
# Remember interface only if interface is set
- kwargs = utils.build_kwargs_dict('interface', instance._interface)
+ kwargs = utils.build_kwargs_dict('interface', instance.interface)
client = identity_client(
session=instance.session,
- region_name=instance._region_name,
+ region_name=instance.region_name,
**kwargs
)
@@ -64,10 +66,10 @@ def build_option_parser(parser):
'--os-identity-api-version',
metavar='<identity-api-version>',
default=utils.env('OS_IDENTITY_API_VERSION'),
- help='Identity API version, default=' +
- DEFAULT_API_VERSION +
- ' (Env: OS_IDENTITY_API_VERSION)')
- return auth.build_auth_plugins_option_parser(parser)
+ help=_('Identity API version, default=%s '
+ '(Env: OS_IDENTITY_API_VERSION)') % DEFAULT_API_VERSION,
+ )
+ return parser
class IdentityClientv2(identity_client_v2.Client):
diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py
index 2afa41f..1f645b7 100644
--- a/openstackclient/identity/common.py
+++ b/openstackclient/identity/common.py
@@ -20,32 +20,75 @@ from keystoneclient.v3 import domains
from keystoneclient.v3 import groups
from keystoneclient.v3 import projects
from keystoneclient.v3 import users
+from osc_lib import exceptions
+from osc_lib import utils
-from openstackclient.common import exceptions
-from openstackclient.common import utils
+from openstackclient.i18n import _
def find_service(identity_client, name_type_or_id):
"""Find a service by id, name or type."""
try:
- # search for the usual ID or name
- return utils.find_resource(identity_client.services, name_type_or_id)
- except exceptions.CommandError:
- try:
- # search for service type
- return identity_client.services.find(type=name_type_or_id)
- # FIXME(dtroyer): This exception should eventually come from
- # common client exceptions
- except identity_exc.NotFound:
- msg = ("No service with a type, name or ID of '%s' exists."
- % name_type_or_id)
- raise exceptions.CommandError(msg)
- except identity_exc.NoUniqueMatch:
- msg = ("Multiple service matches found for '%s', "
- "use an ID to be more specific."
- % name_type_or_id)
- raise exceptions.CommandError(msg)
+ # search for service id
+ return identity_client.services.get(name_type_or_id)
+ except identity_exc.NotFound:
+ # ignore NotFound exception, raise others
+ pass
+
+ try:
+ # search for service name
+ return identity_client.services.find(name=name_type_or_id)
+ except identity_exc.NotFound:
+ pass
+ except identity_exc.NoUniqueMatch:
+ msg = _("Multiple service matches found for '%s', "
+ "use an ID to be more specific.")
+ raise exceptions.CommandError(msg % name_type_or_id)
+
+ try:
+ # search for service type
+ return identity_client.services.find(type=name_type_or_id)
+ except identity_exc.NotFound:
+ msg = _("No service with a type, name or ID of '%s' exists.")
+ raise exceptions.CommandError(msg % name_type_or_id)
+ except identity_exc.NoUniqueMatch:
+ msg = _("Multiple service matches found for '%s', "
+ "use an ID to be more specific.")
+ raise exceptions.CommandError(msg % name_type_or_id)
+
+
+def _get_token_resource(client, resource, parsed_name):
+ """Peek into the user's auth token to get resource IDs
+
+ Look into a user's token to try and find the ID of a domain, project or
+ user, when given the name. Typically non-admin users will interact with
+ the CLI using names. However, by default, keystone does not allow look up
+ by name since it would involve listing all entities. Instead opt to use
+ the correct ID (from the token) instead.
+ :param client: An identity client
+ :param resource: A resource to look at in the token, this may be `domain`,
+ `project_domain`, `user_domain`, `project`, or `user`.
+ :param parsed_name: This is input from parsed_args that the user is hoping
+ to find in the token.
+
+ :returns: The ID of the resource from the token, or the original value from
+ parsed_args if it does not match.
+ """
+
+ try:
+ token = client.auth.client.get_token()
+ token_data = client.tokens.get_token_data(token)
+ token_dict = token_data['token']
+
+ # NOTE(stevemar): If domain is passed, just look at the project domain.
+ if resource == 'domain':
+ token_dict = token_dict['project']
+ obj = token_dict[resource]
+ return obj['id'] if obj['name'] == parsed_name else parsed_name
+ # diaper defense in case parsing the token fails
+ except Exception: # noqa
+ return parsed_name
def _get_domain_id_if_requested(identity_client, domain_name_or_id):
@@ -132,9 +175,9 @@ def add_user_domain_option_to_parser(parser):
parser.add_argument(
'--user-domain',
metavar='<user-domain>',
- help=('Domain the user belongs to (name or ID). '
- 'This can be used in case collisions between user names '
- 'exist.')
+ help=_('Domain the user belongs to (name or ID). '
+ 'This can be used in case collisions between user names '
+ 'exist.'),
)
@@ -142,9 +185,9 @@ def add_group_domain_option_to_parser(parser):
parser.add_argument(
'--group-domain',
metavar='<group-domain>',
- help=('Domain the group belongs to (name or ID). '
- 'This can be used in case collisions between group names '
- 'exist.')
+ help=_('Domain the group belongs to (name or ID). '
+ 'This can be used in case collisions between group names '
+ 'exist.'),
)
@@ -152,9 +195,19 @@ def add_project_domain_option_to_parser(parser):
parser.add_argument(
'--project-domain',
metavar='<project-domain>',
- help=('Domain the project belongs to (name or ID). '
- 'This can be used in case collisions between project names '
- 'exist.')
+ help=_('Domain the project belongs to (name or ID). '
+ 'This can be used in case collisions between project names '
+ 'exist.'),
+ )
+
+
+def add_role_domain_option_to_parser(parser):
+ parser.add_argument(
+ '--role-domain',
+ metavar='<role-domain>',
+ help=_('Domain the role belongs to (name or ID). '
+ 'This must be specified when the name of a domain specific '
+ 'role is used.'),
)
@@ -163,5 +216,6 @@ def add_inherited_option_to_parser(parser):
'--inherited',
action='store_true',
default=False,
- help=('Specifies if the role grant is inheritable to the sub projects')
+ help=_('Specifies if the role grant is inheritable to the sub '
+ 'projects'),
)
diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py
index c8f48cb..7a15cf3 100644
--- a/openstackclient/identity/v2_0/catalog.py
+++ b/openstackclient/identity/v2_0/catalog.py
@@ -13,13 +13,19 @@
"""Identity v2 Service Catalog action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
def _format_endpoints(eps=None):
if not eps:
return ""
@@ -41,13 +47,14 @@ class ListCatalog(command.Lister):
def take_action(self, parsed_args):
- # This is ugly because if auth hasn't happened yet we need
- # to trigger it here.
- sc = self.app.client_manager.session.auth.get_auth_ref(
- self.app.client_manager.session,
- ).service_catalog
+ # Trigger auth if it has not happened yet
+ auth_ref = self.app.client_manager.auth_ref
+ if not auth_ref:
+ raise exceptions.AuthorizationFailure(
+ "Only an authorized user may issue a new token."
+ )
- data = sc.get_data()
+ data = auth_ref.service_catalog.catalog
columns = ('Name', 'Type', 'Endpoints')
return (columns,
(utils.get_dict_properties(
@@ -72,14 +79,15 @@ class ShowCatalog(command.ShowOne):
def take_action(self, parsed_args):
- # This is ugly because if auth hasn't happened yet we need
- # to trigger it here.
- sc = self.app.client_manager.session.auth.get_auth_ref(
- self.app.client_manager.session,
- ).service_catalog
+ # Trigger auth if it has not happened yet
+ auth_ref = self.app.client_manager.auth_ref
+ if not auth_ref:
+ raise exceptions.AuthorizationFailure(
+ "Only an authorized user may issue a new token."
+ )
data = None
- for service in sc.get_data():
+ for service in auth_ref.service_catalog.catalog:
if (service.get('name') == parsed_args.service or
service.get('type') == parsed_args.service):
data = service
@@ -89,8 +97,7 @@ class ShowCatalog(command.ShowOne):
break
if not data:
- self.app.log.error(_('service %s not found\n') %
- parsed_args.service)
- return ([], [])
+ LOG.error(_('service %s not found\n'), parsed_args.service)
+ return ((), ())
return zip(*sorted(six.iteritems(data)))
diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py
index dfd6759..4873cd5 100644
--- a/openstackclient/identity/v2_0/ec2creds.py
+++ b/openstackclient/identity/v2_0/ec2creds.py
@@ -16,13 +16,19 @@
"""Identity v2 EC2 Credentials action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateEC2Creds(command.ShowOne):
"""Create EC2 credentials"""
@@ -85,9 +91,10 @@ class DeleteEC2Creds(command.Command):
def get_parser(self, prog_name):
parser = super(DeleteEC2Creds, self).get_parser(prog_name)
parser.add_argument(
- 'access_key',
+ 'access_keys',
metavar='<access-key>',
- help=_('Credentials access key'),
+ nargs='+',
+ help=_('Credentials access key(s)'),
)
parser.add_argument(
'--user',
@@ -108,7 +115,21 @@ class DeleteEC2Creds(command.Command):
# Get the user from the current auth
user = self.app.client_manager.auth_ref.user_id
- identity_client.ec2.delete(user, parsed_args.access_key)
+ result = 0
+ for access_key in parsed_args.access_keys:
+ try:
+ identity_client.ec2.delete(user, access_key)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete EC2 credentials with "
+ "access key '%(access_key)s': %(e)s")
+ % {'access_key': access_key, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.access_keys)
+ msg = (_("%(result)s of %(total)s EC2 keys failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListEC2Creds(command.Lister):
diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py
index 09ea738..5a3b318 100644
--- a/openstackclient/identity/v2_0/endpoint.py
+++ b/openstackclient/identity/v2_0/endpoint.py
@@ -15,14 +15,20 @@
"""Endpoint action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
class CreateEndpoint(command.ShowOne):
"""Create new endpoint"""
@@ -31,7 +37,7 @@ class CreateEndpoint(command.ShowOne):
parser.add_argument(
'service',
metavar='<service>',
- help=_('New endpoint service (name or ID)'),
+ help=_('Service to be associated with new endpoint (name or ID)'),
)
parser.add_argument(
'--publicurl',
@@ -74,20 +80,36 @@ class CreateEndpoint(command.ShowOne):
class DeleteEndpoint(command.Command):
- """Delete endpoint"""
+ """Delete endpoint(s)"""
def get_parser(self, prog_name):
parser = super(DeleteEndpoint, self).get_parser(prog_name)
parser.add_argument(
- 'endpoint',
+ 'endpoints',
metavar='<endpoint-id>',
- help=_('Endpoint ID to delete'),
+ nargs='+',
+ help=_('Endpoint(s) to delete (ID only)'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- identity_client.endpoints.delete(parsed_args.endpoint)
+
+ result = 0
+ for endpoint in parsed_args.endpoints:
+ try:
+ identity_client.endpoints.delete(endpoint)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete endpoint with "
+ "ID '%(endpoint)s': %(e)s")
+ % {'endpoint': endpoint, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.endpoints)
+ msg = (_("%(result)s of %(total)s endpoints failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListEndpoint(command.Lister):
diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py
index 80f88d7..fc5c920 100644
--- a/openstackclient/identity/v2_0/project.py
+++ b/openstackclient/identity/v2_0/project.py
@@ -15,16 +15,20 @@
"""Identity v2 Project action implementations"""
-import six
+import logging
from keystoneauth1 import exceptions as ks_exc
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import utils
+import six
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateProject(command.ShowOne):
"""Create new project"""
@@ -82,15 +86,15 @@ class CreateProject(command.ShowOne):
enabled=enabled,
**kwargs
)
- except ks_exc.Conflict as e:
+ except ks_exc.Conflict:
if parsed_args.or_show:
project = utils.find_resource(
identity_client.tenants,
parsed_args.name,
)
- self.log.info(_('Returning existing project %s'), project.name)
+ LOG.info(_('Returning existing project %s'), project.name)
else:
- raise e
+ raise
# TODO(stevemar): Remove the line below when we support multitenancy
project._info.pop('parent_id', None)
@@ -190,13 +194,6 @@ class SetProject(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- if (not parsed_args.name
- and not parsed_args.description
- and not parsed_args.enable
- and not parsed_args.property
- and not parsed_args.disable):
- return
-
project = utils.find_resource(
identity_client.tenants,
parsed_args.project,
@@ -245,7 +242,7 @@ class ShowProject(command.ShowOne):
parsed_args.project,
)
info.update(project._info)
- except ks_exc.Forbidden as e:
+ except ks_exc.Forbidden:
auth_ref = self.app.client_manager.auth_ref
if (
parsed_args.project == auth_ref.project_id or
@@ -259,7 +256,7 @@ class ShowProject(command.ShowOne):
'enabled': True,
}
else:
- raise e
+ raise
# TODO(stevemar): Remove the line below when we support multitenancy
info.pop('parent_id', None)
@@ -296,7 +293,6 @@ class UnsetProject(command.Command):
metavar='<key>',
action='append',
default=[],
- required=True,
help=_('Unset a project property '
'(repeat option to unset multiple properties)'),
)
@@ -308,11 +304,8 @@ class UnsetProject(command.Command):
identity_client.tenants,
parsed_args.project,
)
- if not parsed_args.property:
- self.app.log.error(_("No changes requested\n"))
- else:
- kwargs = project._info
- for key in parsed_args.property:
- if key in kwargs:
- kwargs[key] = None
- identity_client.tenants.update(project.id, **kwargs)
+ kwargs = project._info
+ for key in parsed_args.property:
+ if key in kwargs:
+ kwargs[key] = None
+ identity_client.tenants.update(project.id, **kwargs)
diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py
index 6b014d8..b4b67ba 100644
--- a/openstackclient/identity/v2_0/role.py
+++ b/openstackclient/identity/v2_0/role.py
@@ -15,16 +15,20 @@
"""Identity v2 Role action implementations"""
-import six
+import logging
from keystoneauth1 import exceptions as ks_exc
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+import six
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class AddRole(command.ShowOne):
"""Add role to project:user"""
@@ -89,15 +93,15 @@ class CreateRole(command.ShowOne):
identity_client = self.app.client_manager.identity
try:
role = identity_client.roles.create(parsed_args.role_name)
- except ks_exc.Conflict as e:
+ except ks_exc.Conflict:
if parsed_args.or_show:
role = utils.find_resource(
identity_client.roles,
parsed_args.role_name,
)
- self.log.info(_('Returning existing role %s'), role.name)
+ LOG.info(_('Returning existing role %s'), role.name)
else:
- raise e
+ raise
info = {}
info.update(role._info)
@@ -146,6 +150,15 @@ class ListRole(command.Lister):
return parser
def take_action(self, parsed_args):
+
+ def _deprecated():
+ # NOTE(henry-nash): Deprecated as of Newton, so we should remove
+ # this in the 'P' release.
+ self.log.warning(_('Listing assignments using role list is '
+ 'deprecated as of the Newton release. Use role '
+ 'assignment list --user <user-name> --project '
+ '<project-name> --names instead.'))
+
identity_client = self.app.client_manager.identity
auth_ref = self.app.client_manager.auth_ref
@@ -162,6 +175,7 @@ class ListRole(command.Lister):
identity_client.projects,
parsed_args.project,
)
+ _deprecated()
data = identity_client.roles.roles_for_user(user.id, project.id)
elif parsed_args.user:
@@ -177,6 +191,7 @@ class ListRole(command.Lister):
else:
msg = _("Project must be specified")
raise exceptions.CommandError(msg)
+ _deprecated()
data = identity_client.roles.roles_for_user(user.id, project.id)
elif parsed_args.project:
project = utils.find_resource(
@@ -191,6 +206,7 @@ class ListRole(command.Lister):
else:
msg = _("User must be specified")
raise exceptions.CommandError(msg)
+ _deprecated()
data = identity_client.roles.roles_for_user(user.id, project.id)
if parsed_args.user or parsed_args.project:
@@ -231,19 +247,24 @@ class ListUserRole(command.Lister):
# Project and user are required, if not included in command args
# default to the values used for authentication. For token-flow
# authentication they must be included on the command line.
+ if (not parsed_args.project and
+ self.app.client_manager.auth_ref.project_id):
+ parsed_args.project = auth_ref.project_id
if not parsed_args.project:
- if self.app.client_manager.auth_ref:
- parsed_args.project = auth_ref.project_id
- else:
- msg = _("Project must be specified")
- raise exceptions.CommandError(msg)
+ msg = _("Project must be specified")
+ raise exceptions.CommandError(msg)
+
+ if (not parsed_args.user and
+ self.app.client_manager.auth_ref.user_id):
+ parsed_args.user = auth_ref.user_id
if not parsed_args.user:
- if self.app.client_manager.auth_ref:
- parsed_args.user = auth_ref.user_id
- else:
- msg = _("User must be specified")
- raise exceptions.CommandError(msg)
+ msg = _("User must be specified")
+ raise exceptions.CommandError(msg)
+ self.log.warning(_('Listing assignments using user role list is '
+ 'deprecated as of the Newton release. Use role '
+ 'assignment list --user <user-name> --project '
+ '<project-name> --names instead.'))
project = utils.find_resource(
identity_client.tenants,
parsed_args.project,
diff --git a/openstackclient/identity/v2_0/role_assignment.py b/openstackclient/identity/v2_0/role_assignment.py
new file mode 100644
index 0000000..44f55c3
--- /dev/null
+++ b/openstackclient/identity/v2_0/role_assignment.py
@@ -0,0 +1,114 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Identity v2 Assignment action implementations """
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.i18n import _ # noqa
+
+
+class ListRoleAssignment(command.Lister):
+ """List role assignments"""
+
+ def get_parser(self, prog_name):
+ parser = super(ListRoleAssignment, self).get_parser(prog_name)
+ parser.add_argument(
+ '--user',
+ metavar='<user>',
+ help='User to filter (name or ID)',
+ )
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help='Project to filter (name or ID)',
+ )
+ parser.add_argument(
+ '--names',
+ action="store_true",
+ help='Display names instead of IDs',
+ )
+ parser.add_argument(
+ '--auth-user',
+ action="store_true",
+ dest='authuser',
+ help='Only list assignments for the authenticated user',
+ )
+ parser.add_argument(
+ '--auth-project',
+ action="store_true",
+ dest='authproject',
+ help='Only list assignments for the project to which the '
+ 'authenticated user\'s token is scoped',
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ identity_client = self.app.client_manager.identity
+ auth_ref = self.app.client_manager.auth_ref
+
+ include_names = True if parsed_args.names else False
+
+ user = None
+ if parsed_args.user:
+ user = utils.find_resource(
+ identity_client.users,
+ parsed_args.user,
+ )
+ elif parsed_args.authuser:
+ if auth_ref:
+ user = utils.find_resource(
+ identity_client.users,
+ auth_ref.user_id
+ )
+
+ project = None
+ if parsed_args.project:
+ project = utils.find_resource(
+ identity_client.projects,
+ parsed_args.project,
+ )
+ elif parsed_args.authproject:
+ if auth_ref:
+ project = utils.find_resource(
+ identity_client.projects,
+ auth_ref.project_id
+ )
+
+ # If user or project is not specified, we would ideally list all
+ # relevant assignments in the system (to be compatible with v3).
+ # However, there is no easy way of doing that in v2.
+ if not user or not project:
+ msg = _("Project and User must be specified")
+ raise exceptions.CommandError(msg)
+ else:
+ data = identity_client.roles.roles_for_user(user.id, project.id)
+
+ columns = ('Role', 'User', 'Project')
+ for user_role in data:
+ if include_names:
+ setattr(user_role, 'role', user_role.name)
+ user_role.user = user.name
+ user_role.project = project.name
+ else:
+ setattr(user_role, 'role', user_role.id)
+ user_role.user = user.id
+ user_role.project = project.id
+
+ return (columns,
+ (utils.get_item_properties(
+ s, columns,
+ formatters={},
+ ) for s in data))
diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py
index 7fe66d9..e318643 100644
--- a/openstackclient/identity/v2_0/service.py
+++ b/openstackclient/identity/v2_0/service.py
@@ -16,15 +16,20 @@
"""Service action implementations"""
import argparse
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
class CreateService(command.ShowOne):
"""Create new service"""
@@ -68,8 +73,8 @@ class CreateService(command.ShowOne):
# display deprecation message.
elif type:
name = type_or_name
- self.log.warning(_('The argument --type is deprecated, use service'
- ' create --name <service-name> type instead.'))
+ LOG.warning(_('The argument --type is deprecated, use service'
+ ' create --name <service-name> type instead.'))
# If --name option is present the positional is handled as <type>.
# Making --type optional is new, but back-compatible
elif name:
@@ -86,21 +91,37 @@ class CreateService(command.ShowOne):
class DeleteService(command.Command):
- """Delete service"""
+ """Delete service(s)"""
def get_parser(self, prog_name):
parser = super(DeleteService, self).get_parser(prog_name)
parser.add_argument(
- 'service',
+ 'services',
metavar='<service>',
- help=_('Service to delete (name or ID)'),
+ nargs='+',
+ help=_('Service(s) to delete (type, name or ID)'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- service = common.find_service(identity_client, parsed_args.service)
- identity_client.services.delete(service.id)
+
+ result = 0
+ for service in parsed_args.services:
+ try:
+ service = common.find_service(identity_client, service)
+ identity_client.services.delete(service.id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete service with "
+ "name or ID '%(service)s': %(e)s")
+ % {'service': service, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.services)
+ msg = (_("%(result)s of %(total)s services failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListService(command.Lister):
diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py
index f435d7c..56f920f 100644
--- a/openstackclient/identity/v2_0/token.py
+++ b/openstackclient/identity/v2_0/token.py
@@ -15,9 +15,10 @@
"""Identity v2 Token action implementations"""
+from osc_lib.command import command
+from osc_lib import exceptions
import six
-from openstackclient.common import command
from openstackclient.i18n import _
@@ -32,11 +33,21 @@ class IssueToken(command.ShowOne):
return parser
def take_action(self, parsed_args):
+ auth_ref = self.app.client_manager.auth_ref
+ if not auth_ref:
+ raise exceptions.AuthorizationFailure(
+ "Only an authorized user may issue a new token.")
- token = self.app.client_manager.auth_ref.service_catalog.get_token()
- if 'tenant_id' in token:
- token['project_id'] = token.pop('tenant_id')
- return zip(*sorted(six.iteritems(token)))
+ data = {}
+ if auth_ref.auth_token:
+ data['id'] = auth_ref.auth_token
+ if auth_ref.expires:
+ data['expires'] = auth_ref.expires
+ if auth_ref.project_id:
+ data['project_id'] = auth_ref.project_id
+ if auth_ref.user_id:
+ data['user_id'] = auth_ref.user_id
+ return zip(*sorted(six.iteritems(data)))
class RevokeToken(command.Command):
diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py
index f8f5df2..d207515 100644
--- a/openstackclient/identity/v2_0/user.py
+++ b/openstackclient/identity/v2_0/user.py
@@ -15,15 +15,19 @@
"""Identity v2.0 User action implementations"""
-import six
+import logging
from keystoneauth1 import exceptions as ks_exc
+from osc_lib.command import command
+from osc_lib import utils
+import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateUser(command.ShowOne):
"""Create new user"""
@@ -98,15 +102,15 @@ class CreateUser(command.ShowOne):
tenant_id=project_id,
enabled=enabled,
)
- except ks_exc.Conflict as e:
+ except ks_exc.Conflict:
if parsed_args.or_show:
user = utils.find_resource(
identity_client.users,
parsed_args.name,
)
- self.log.info(_('Returning existing user %s'), user.name)
+ LOG.info(_('Returning existing user %s'), user.name)
else:
- raise e
+ raise
# NOTE(dtroyer): The users.create() method wants 'tenant_id' but
# the returned resource has 'tenantId'. Sigh.
@@ -241,7 +245,7 @@ class SetUser(command.Command):
parser.add_argument(
'user',
metavar='<user>',
- help=_('User to change (name or ID)'),
+ help=_('User to modify (name or ID)'),
)
parser.add_argument(
'--name',
@@ -288,15 +292,6 @@ class SetUser(command.Command):
if parsed_args.password_prompt:
parsed_args.password = utils.get_password(self.app.stdin)
- if (not parsed_args.name
- and not parsed_args.name
- and not parsed_args.password
- and not parsed_args.email
- and not parsed_args.project
- and not parsed_args.enable
- and not parsed_args.disable):
- return
-
user = utils.find_resource(
identity_client.users,
parsed_args.user,
@@ -354,7 +349,7 @@ class ShowUser(command.ShowOne):
parsed_args.user,
)
info.update(user._info)
- except ks_exc.Forbidden as e:
+ except ks_exc.Forbidden:
auth_ref = self.app.client_manager.auth_ref
if (
parsed_args.user == auth_ref.user_id or
@@ -369,7 +364,7 @@ class ShowUser(command.ShowOne):
'enabled': True,
}
else:
- raise e
+ raise
if 'tenantId' in info:
info.update(
diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py
index 4c79469..a62d0a9 100644
--- a/openstackclient/identity/v3/catalog.py
+++ b/openstackclient/identity/v3/catalog.py
@@ -13,13 +13,19 @@
"""Identity v3 Service Catalog action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
def _format_endpoints(eps=None):
if not eps:
return ""
@@ -36,13 +42,14 @@ class ListCatalog(command.Lister):
def take_action(self, parsed_args):
- # This is ugly because if auth hasn't happened yet we need
- # to trigger it here.
- sc = self.app.client_manager.session.auth.get_auth_ref(
- self.app.client_manager.session,
- ).service_catalog
+ # Trigger auth if it has not happened yet
+ auth_ref = self.app.client_manager.auth_ref
+ if not auth_ref:
+ raise exceptions.AuthorizationFailure(
+ "Only an authorized user may issue a new token."
+ )
- data = sc.get_data()
+ data = auth_ref.service_catalog.catalog
columns = ('Name', 'Type', 'Endpoints')
return (columns,
(utils.get_dict_properties(
@@ -67,14 +74,15 @@ class ShowCatalog(command.ShowOne):
def take_action(self, parsed_args):
- # This is ugly because if auth hasn't happened yet we need
- # to trigger it here.
- sc = self.app.client_manager.session.auth.get_auth_ref(
- self.app.client_manager.session,
- ).service_catalog
+ # Trigger auth if it has not happened yet
+ auth_ref = self.app.client_manager.auth_ref
+ if not auth_ref:
+ raise exceptions.AuthorizationFailure(
+ "Only an authorized user may issue a new token."
+ )
data = None
- for service in sc.get_data():
+ for service in auth_ref.service_catalog.catalog:
if (service.get('name') == parsed_args.service or
service.get('type') == parsed_args.service):
data = dict(service)
@@ -84,8 +92,7 @@ class ShowCatalog(command.ShowOne):
break
if not data:
- self.app.log.error(_('service %s not found\n') %
- parsed_args.service)
- return ([], [])
+ LOG.error(_('service %s not found\n'), parsed_args.service)
+ return ((), ())
return zip(*sorted(six.iteritems(data)))
diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py
index a062b74..b41a37c 100644
--- a/openstackclient/identity/v3/consumer.py
+++ b/openstackclient/identity/v3/consumer.py
@@ -15,14 +15,19 @@
"""Identity v3 Consumer action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-import sys
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateConsumer(command.ShowOne):
"""Create new consumer"""
@@ -45,22 +50,37 @@ class CreateConsumer(command.ShowOne):
class DeleteConsumer(command.Command):
- """Delete consumer"""
+ """Delete consumer(s)"""
def get_parser(self, prog_name):
parser = super(DeleteConsumer, self).get_parser(prog_name)
parser.add_argument(
'consumer',
metavar='<consumer>',
- help=_('Consumer to delete'),
+ nargs='+',
+ help=_('Consumer(s) to delete'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- consumer = utils.find_resource(
- identity_client.oauth1.consumers, parsed_args.consumer)
- identity_client.oauth1.consumers.delete(consumer.id)
+ result = 0
+ for i in parsed_args.consumer:
+ try:
+ consumer = utils.find_resource(
+ identity_client.oauth1.consumers, i)
+ identity_client.oauth1.consumers.delete(consumer.id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete consumer with name or "
+ "ID '%(consumer)s': %(e)s")
+ % {'consumer': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.consumer)
+ msg = (_("%(result)s of %(total)s consumers failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListConsumer(command.Lister):
@@ -101,10 +121,6 @@ class SetConsumer(command.Command):
if parsed_args.description:
kwargs['description'] = parsed_args.description
- if not len(kwargs):
- sys.stdout.write(_('Consumer not updated, no arguments present\n'))
- return
-
consumer = identity_client.oauth1.consumers.update(
consumer.id, **kwargs)
diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py
index 9901347..0ef94cf 100644
--- a/openstackclient/identity/v3/credential.py
+++ b/openstackclient/identity/v3/credential.py
@@ -15,22 +15,28 @@
"""Identity v3 Credential action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateCredential(command.ShowOne):
- """Create credential command"""
+ """Create new credential"""
def get_parser(self, prog_name):
parser = super(CreateCredential, self).get_parser(prog_name)
parser.add_argument(
'user',
metavar='<user>',
- help=_('Name or ID of user that owns the credential'),
+ help=_('user that owns the credential (name or ID)'),
)
parser.add_argument(
'--type',
@@ -47,8 +53,8 @@ class CreateCredential(command.ShowOne):
parser.add_argument(
'--project',
metavar='<project>',
- help=_('Project name or ID which limits the '
- 'scope of the credential'),
+ help=_('Project which limits the scope of '
+ 'the credential (name or ID)'),
)
return parser
@@ -72,24 +78,39 @@ class CreateCredential(command.ShowOne):
class DeleteCredential(command.Command):
- """Delete credential command"""
+ """Delete credential(s)"""
def get_parser(self, prog_name):
parser = super(DeleteCredential, self).get_parser(prog_name)
parser.add_argument(
'credential',
metavar='<credential-id>',
- help=_('ID of credential to delete'),
+ nargs='+',
+ help=_('ID of credential(s) to delete'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- identity_client.credentials.delete(parsed_args.credential)
+ result = 0
+ for i in parsed_args.credential:
+ try:
+ identity_client.credentials.delete(i)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete credentials with "
+ "ID '%(credential)s': %(e)s")
+ % {'credential': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.credential)
+ msg = (_("%(result)s of %(total)s credential failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListCredential(command.Lister):
- """List credential command"""
+ """List credentials"""
def take_action(self, parsed_args):
columns = ('ID', 'Type', 'User ID', 'Blob', 'Project ID')
@@ -103,7 +124,7 @@ class ListCredential(command.Lister):
class SetCredential(command.Command):
- """Set credential command"""
+ """Set credential properties"""
def get_parser(self, prog_name):
parser = super(SetCredential, self).get_parser(prog_name)
@@ -116,7 +137,7 @@ class SetCredential(command.Command):
'--user',
metavar='<user>',
required=True,
- help=_('Name or ID of user that owns the credential'),
+ help=_('User that owns the credential (name or ID)'),
)
parser.add_argument(
'--type',
@@ -134,8 +155,8 @@ class SetCredential(command.Command):
parser.add_argument(
'--project',
metavar='<project>',
- help=_('Project name or ID which limits the '
- 'scope of the credential'),
+ help=_('Project which limits the scope of '
+ 'the credential (name or ID)'),
)
return parser
@@ -159,7 +180,7 @@ class SetCredential(command.Command):
class ShowCredential(command.ShowOne):
- """Show credential command"""
+ """Display credential details"""
def get_parser(self, prog_name):
parser = super(ShowCredential, self).get_parser(prog_name)
diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py
index c345028..76e47d3 100644
--- a/openstackclient/identity/v3/domain.py
+++ b/openstackclient/identity/v3/domain.py
@@ -15,14 +15,19 @@
"""Identity v3 Domain action implementations"""
-import six
-import sys
+import logging
from keystoneauth1 import exceptions as ks_exc
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
+from openstackclient.identity import common
+
+
+LOG = logging.getLogger(__name__)
class CreateDomain(command.ShowOne):
@@ -71,35 +76,49 @@ class CreateDomain(command.ShowOne):
description=parsed_args.description,
enabled=enabled,
)
- except ks_exc.Conflict as e:
+ except ks_exc.Conflict:
if parsed_args.or_show:
domain = utils.find_resource(identity_client.domains,
parsed_args.name)
- self.log.info(_('Returning existing domain %s'), domain.name)
+ LOG.info(_('Returning existing domain %s'), domain.name)
else:
- raise e
+ raise
domain._info.pop('links')
return zip(*sorted(six.iteritems(domain._info)))
class DeleteDomain(command.Command):
- """Delete domain"""
+ """Delete domain(s)"""
def get_parser(self, prog_name):
parser = super(DeleteDomain, self).get_parser(prog_name)
parser.add_argument(
'domain',
metavar='<domain>',
- help=_('Domain to delete (name or ID)'),
+ nargs='+',
+ help=_('Domain(s) to delete (name or ID)'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- domain = utils.find_resource(identity_client.domains,
- parsed_args.domain)
- identity_client.domains.delete(domain.id)
+ result = 0
+ for i in parsed_args.domain:
+ try:
+ domain = utils.find_resource(identity_client.domains, i)
+ identity_client.domains.delete(domain.id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete domain with name or "
+ "ID '%(domain)s': %(e)s")
+ % {'domain': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.domain)
+ msg = (_("%(result)s of %(total)s domains failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListDomain(command.Lister):
@@ -163,9 +182,6 @@ class SetDomain(command.Command):
if parsed_args.disable:
kwargs['enabled'] = False
- if not kwargs:
- sys.stdout.write(_("Domain not updated, no arguments present\n"))
- return
identity_client.domains.update(domain.id, **kwargs)
@@ -183,8 +199,12 @@ class ShowDomain(command.ShowOne):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
+
+ domain_str = common._get_token_resource(identity_client, 'domain',
+ parsed_args.domain)
+
domain = utils.find_resource(identity_client.domains,
- parsed_args.domain)
+ domain_str)
domain._info.pop('links')
return zip(*sorted(six.iteritems(domain._info)))
diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py
index 859ec2a..7ad0171 100644
--- a/openstackclient/identity/v3/ec2creds.py
+++ b/openstackclient/identity/v3/ec2creds.py
@@ -12,14 +12,20 @@
"""Identity v3 EC2 Credentials action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
def _determine_ec2_user(parsed_args, client_manager):
"""Determine a user several different ways.
@@ -113,7 +119,8 @@ class DeleteEC2Creds(command.Command):
parser.add_argument(
'access_key',
metavar='<access-key>',
- help=_('Credentials access key'),
+ nargs='+',
+ help=_('Credentials access key(s)'),
)
parser.add_argument(
'--user',
@@ -126,7 +133,21 @@ class DeleteEC2Creds(command.Command):
def take_action(self, parsed_args):
client_manager = self.app.client_manager
user = _determine_ec2_user(parsed_args, client_manager)
- client_manager.identity.ec2.delete(user, parsed_args.access_key)
+ result = 0
+ for i in parsed_args.access_key:
+ try:
+ client_manager.identity.ec2.delete(user, i)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete EC2 credentials with "
+ "access key '%(access_key)s': %(e)s")
+ % {'access_key': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.access_key)
+ msg = (_("%(result)s of %(total)s EC2 keys failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListEC2Creds(command.Lister):
diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py
index 39022d2..73b37a4 100644
--- a/openstackclient/identity/v3/endpoint.py
+++ b/openstackclient/identity/v3/endpoint.py
@@ -15,15 +15,20 @@
"""Identity v3 Endpoint action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-import sys
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
def get_service_name(service):
if hasattr(service, 'name'):
return service.name
@@ -39,7 +44,7 @@ class CreateEndpoint(command.ShowOne):
parser.add_argument(
'service',
metavar='<service>',
- help=_('New endpoint service (name or ID)'),
+ help=_('Service to be associated with new endpoint (name or ID)'),
)
parser.add_argument(
'interface',
@@ -94,22 +99,37 @@ class CreateEndpoint(command.ShowOne):
class DeleteEndpoint(command.Command):
- """Delete endpoint"""
+ """Delete endpoint(s)"""
def get_parser(self, prog_name):
parser = super(DeleteEndpoint, self).get_parser(prog_name)
parser.add_argument(
'endpoint',
metavar='<endpoint-id>',
- help=_('Endpoint ID to delete'),
+ nargs='+',
+ help=_('Endpoint(s) to delete (ID only)'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- endpoint_id = utils.find_resource(identity_client.endpoints,
- parsed_args.endpoint).id
- identity_client.endpoints.delete(endpoint_id)
+ result = 0
+ for i in parsed_args.endpoint:
+ try:
+ endpoint_id = utils.find_resource(
+ identity_client.endpoints, i).id
+ identity_client.endpoints.delete(endpoint_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete endpoint with "
+ "ID '%(endpoint)s': %(e)s")
+ % {'endpoint': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.endpoint)
+ msg = (_("%(result)s of %(total)s endpoints failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListEndpoint(command.Lister):
@@ -120,7 +140,7 @@ class ListEndpoint(command.Lister):
parser.add_argument(
'--service',
metavar='<service>',
- help=_('Filter by service'),
+ help=_('Filter by service (name or ID)'),
)
parser.add_argument(
'--interface',
@@ -168,7 +188,7 @@ class SetEndpoint(command.Command):
parser.add_argument(
'endpoint',
metavar='<endpoint-id>',
- help=_('Endpoint ID to modify'),
+ help=_('Endpoint to modify (ID only)'),
)
parser.add_argument(
'--region',
@@ -211,12 +231,6 @@ class SetEndpoint(command.Command):
endpoint = utils.find_resource(identity_client.endpoints,
parsed_args.endpoint)
- if (not parsed_args.interface and not parsed_args.url
- and not parsed_args.service and not parsed_args.region
- and not parsed_args.enabled and not parsed_args.disabled):
- sys.stdout.write(_("Endpoint not updated, no arguments present\n"))
- return
-
service_id = None
if parsed_args.service:
service = common.find_service(identity_client, parsed_args.service)
diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py
index c0f4bc9..3fde902 100644
--- a/openstackclient/identity/v3/federation_protocol.py
+++ b/openstackclient/identity/v3/federation_protocol.py
@@ -14,13 +14,19 @@
"""Identity v3 Protocols actions implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateProtocol(command.ShowOne):
"""Create new federation protocol"""
@@ -66,14 +72,15 @@ class CreateProtocol(command.ShowOne):
class DeleteProtocol(command.Command):
- """Delete federation protocol"""
+ """Delete federation protocol(s)"""
def get_parser(self, prog_name):
parser = super(DeleteProtocol, self).get_parser(prog_name)
parser.add_argument(
'federation_protocol',
metavar='<federation-protocol>',
- help=_('Federation protocol to delete (name or ID)'),
+ nargs='+',
+ help=_('Federation protocol(s) to delete (name or ID)'),
)
parser.add_argument(
'--identity-provider',
@@ -87,8 +94,22 @@ class DeleteProtocol(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- identity_client.federation.protocols.delete(
- parsed_args.identity_provider, parsed_args.federation_protocol)
+ result = 0
+ for i in parsed_args.federation_protocol:
+ try:
+ identity_client.federation.protocols.delete(
+ parsed_args.identity_provider, i)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete federation protocol "
+ "with name or ID '%(protocol)s': %(e)s")
+ % {'protocol': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.federation_protocol)
+ msg = (_("%(result)s of %(total)s federation protocols failed"
+ " to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListProtocols(command.Lister):
@@ -144,10 +165,6 @@ class SetProtocol(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- if not parsed_args.mapping:
- self.app.log.error(_("No changes requested"))
- return
-
protocol = identity_client.federation.protocols.update(
parsed_args.identity_provider, parsed_args.federation_protocol,
parsed_args.mapping)
diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py
index fdb94da..f780810 100644
--- a/openstackclient/identity/v3/group.py
+++ b/openstackclient/identity/v3/group.py
@@ -15,17 +15,21 @@
"""Group action implementations"""
-import six
+import logging
import sys
from keystoneauth1 import exceptions as ks_exc
+from osc_lib.command import command
+from osc_lib import utils
+import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
class AddUserToGroup(command.Command):
"""Add user to group"""
@@ -156,14 +160,14 @@ class CreateGroup(command.ShowOne):
name=parsed_args.name,
domain=domain,
description=parsed_args.description)
- except ks_exc.Conflict as e:
+ except ks_exc.Conflict:
if parsed_args.or_show:
group = utils.find_resource(identity_client.groups,
parsed_args.name,
domain_id=domain)
- self.log.info(_('Returning existing group %s'), group.name)
+ LOG.info(_('Returning existing group %s'), group.name)
else:
- raise e
+ raise
group._info.pop('links')
return zip(*sorted(six.iteritems(group._info)))
@@ -339,9 +343,6 @@ class SetGroup(command.Command):
if parsed_args.description:
kwargs['description'] = parsed_args.description
- if not len(kwargs):
- sys.stderr.write("Group not updated, no arguments present\n")
- return
identity_client.groups.update(group.id, **kwargs)
diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py
index 3749aa3..b6b0318 100644
--- a/openstackclient/identity/v3/identity_provider.py
+++ b/openstackclient/identity/v3/identity_provider.py
@@ -13,13 +13,19 @@
"""Identity v3 IdentityProvider action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateIdentityProvider(command.ShowOne):
"""Create new identity provider"""
@@ -88,21 +94,35 @@ class CreateIdentityProvider(command.ShowOne):
class DeleteIdentityProvider(command.Command):
- """Delete identity provider"""
+ """Delete identity provider(s)"""
def get_parser(self, prog_name):
parser = super(DeleteIdentityProvider, self).get_parser(prog_name)
parser.add_argument(
'identity_provider',
metavar='<identity-provider>',
- help=_('Identity provider to delete'),
+ nargs='+',
+ help=_('Identity provider(s) to delete'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- identity_client.federation.identity_providers.delete(
- parsed_args.identity_provider)
+ result = 0
+ for i in parsed_args.identity_provider:
+ try:
+ identity_client.federation.identity_providers.delete(i)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete identity providers with "
+ "name or ID '%(provider)s': %(e)s")
+ % {'provider': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.identity_provider)
+ msg = (_("%(result)s of %(total)s identity providers failed"
+ " to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListIdentityProvider(command.Lister):
@@ -164,14 +184,6 @@ class SetIdentityProvider(command.Command):
def take_action(self, parsed_args):
federation_client = self.app.client_manager.identity.federation
- # Basic argument checking
- if (not parsed_args.enable and not parsed_args.disable and
- not parsed_args.remote_id and
- not parsed_args.remote_id_file and
- not parsed_args.description):
- self.log.error(_('No changes requested'))
- return (None, None)
-
# Always set remote_ids if either is passed in
if parsed_args.remote_id_file:
file_content = utils.read_blob_file_contents(
@@ -192,11 +204,10 @@ class SetIdentityProvider(command.Command):
if parsed_args.remote_id_file or parsed_args.remote_id:
kwargs['remote_ids'] = remote_ids
- identity_provider = federation_client.identity_providers.update(
- parsed_args.identity_provider, **kwargs)
-
- identity_provider._info.pop('links', None)
- return zip(*sorted(six.iteritems(identity_provider._info)))
+ federation_client.identity_providers.update(
+ parsed_args.identity_provider,
+ **kwargs
+ )
class ShowIdentityProvider(command.ShowOne):
diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py
index c45796e..09181a0 100644
--- a/openstackclient/identity/v3/mapping.py
+++ b/openstackclient/identity/v3/mapping.py
@@ -16,15 +16,19 @@
"""Identity v3 federation mapping action implementations"""
import json
+import logging
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class _RulesReader(object):
"""Helper class capable of reading rules from files"""
@@ -107,21 +111,35 @@ class CreateMapping(command.ShowOne, _RulesReader):
class DeleteMapping(command.Command):
- """Delete mapping"""
+ """Delete mapping(s)"""
def get_parser(self, prog_name):
parser = super(DeleteMapping, self).get_parser(prog_name)
parser.add_argument(
'mapping',
metavar='<mapping>',
- help=_('Mapping to delete'),
+ nargs='+',
+ help=_('Mapping(s) to delete'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
-
- identity_client.federation.mappings.delete(parsed_args.mapping)
+ result = 0
+ for i in parsed_args.mapping:
+ try:
+ identity_client.federation.mappings.delete(i)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete mapping with name or "
+ "ID '%(mapping)s': %(e)s")
+ % {'mapping': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.mapping)
+ msg = (_("%(result)s of %(total)s mappings failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListMapping(command.Lister):
@@ -158,10 +176,6 @@ class SetMapping(command.Command, _RulesReader):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- if not parsed_args.rules:
- self.app.log.error(_("No changes requested"))
- return
-
rules = self._read_rules(parsed_args.rules)
mapping = identity_client.federation.mappings.update(
diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py
index 74a783b..596eae0 100644
--- a/openstackclient/identity/v3/policy.py
+++ b/openstackclient/identity/v3/policy.py
@@ -15,14 +15,19 @@
"""Identity v3 Policy action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-import sys
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreatePolicy(command.ShowOne):
"""Create new policy"""
@@ -56,20 +61,35 @@ class CreatePolicy(command.ShowOne):
class DeletePolicy(command.Command):
- """Delete policy"""
+ """Delete policy(s)"""
def get_parser(self, prog_name):
parser = super(DeletePolicy, self).get_parser(prog_name)
parser.add_argument(
'policy',
metavar='<policy>',
- help=_('Policy to delete'),
+ nargs='+',
+ help=_('Policy(s) to delete'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- identity_client.policies.delete(parsed_args.policy)
+ result = 0
+ for i in parsed_args.policy:
+ try:
+ identity_client.policies.delete(i)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete policy with name or "
+ "ID '%(policy)s': %(e)s")
+ % {'policy': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.policy)
+ msg = (_("%(result)s of %(total)s policys failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListPolicy(command.Lister):
@@ -135,9 +155,6 @@ class SetPolicy(command.Command):
if parsed_args.type:
kwargs['type'] = parsed_args.type
- if not kwargs:
- sys.stdout.write(_('Policy not updated, no arguments present\n'))
- return
identity_client.policies.update(parsed_args.policy, **kwargs)
diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py
index acf639f..56c4fbc 100644
--- a/openstackclient/identity/v3/project.py
+++ b/openstackclient/identity/v3/project.py
@@ -15,17 +15,21 @@
"""Project action implementations"""
-import six
+import logging
from keystoneauth1 import exceptions as ks_exc
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import utils
+import six
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
class CreateProject(command.ShowOne):
"""Create new project"""
@@ -107,14 +111,14 @@ class CreateProject(command.ShowOne):
enabled=enabled,
**kwargs
)
- except ks_exc.Conflict as e:
+ except ks_exc.Conflict:
if parsed_args.or_show:
project = utils.find_resource(identity_client.projects,
parsed_args.name,
domain_id=domain)
- self.log.info(_('Returning existing project %s'), project.name)
+ LOG.info(_('Returning existing project %s'), project.name)
else:
- raise e
+ raise
project._info.pop('links')
return zip(*sorted(six.iteritems(project._info)))
@@ -259,13 +263,6 @@ class SetProject(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- if (not parsed_args.name
- and not parsed_args.domain
- and not parsed_args.description
- and not parsed_args.enable
- and not parsed_args.property
- and not parsed_args.disable):
- return
project = common.find_project(identity_client,
parsed_args.project,
parsed_args.domain)
@@ -317,18 +314,21 @@ class ShowProject(command.ShowOne):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
+ project_str = common._get_token_resource(identity_client, 'project',
+ parsed_args.project)
+
if parsed_args.domain:
domain = common.find_domain(identity_client, parsed_args.domain)
project = utils.find_resource(
identity_client.projects,
- parsed_args.project,
+ project_str,
domain_id=domain.id,
parents_as_list=parsed_args.parents,
subtree_as_list=parsed_args.children)
else:
project = utils.find_resource(
identity_client.projects,
- parsed_args.project,
+ project_str,
parents_as_list=parsed_args.parents,
subtree_as_list=parsed_args.children)
diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py
index 053e4b3..b7c51f9 100644
--- a/openstackclient/identity/v3/region.py
+++ b/openstackclient/identity/v3/region.py
@@ -13,13 +13,19 @@
"""Identity v3 Region action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateRegion(command.ShowOne):
"""Create new region"""
@@ -60,21 +66,35 @@ class CreateRegion(command.ShowOne):
class DeleteRegion(command.Command):
- """Delete region"""
+ """Delete region(s)"""
def get_parser(self, prog_name):
parser = super(DeleteRegion, self).get_parser(prog_name)
parser.add_argument(
'region',
metavar='<region-id>',
- help=_('Region ID to delete'),
+ nargs='+',
+ help=_('Region ID(s) to delete'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
-
- identity_client.regions.delete(parsed_args.region)
+ result = 0
+ for i in parsed_args.region:
+ try:
+ identity_client.regions.delete(i)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete region with "
+ "ID '%(region)s': %(e)s")
+ % {'region': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.region)
+ msg = (_("%(result)s of %(total)s regions failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListRegion(command.Lister):
@@ -132,8 +152,6 @@ class SetRegion(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- if not parsed_args.parent_region and not parsed_args.description:
- return
kwargs = {}
if parsed_args.description:
kwargs['description'] = parsed_args.description
diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py
index e7078f4..8b91174 100644
--- a/openstackclient/identity/v3/role.py
+++ b/openstackclient/identity/v3/role.py
@@ -15,17 +15,21 @@
"""Identity v3 Role action implementations"""
-import six
+import logging
import sys
from keystoneauth1 import exceptions as ks_exc
+from osc_lib.command import command
+from osc_lib import utils
+import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
def _add_identity_and_resource_options_to_parser(parser):
domain_or_project = parser.add_mutually_exclusive_group()
domain_or_project.add_argument(
@@ -105,7 +109,7 @@ def _process_identity_and_resource_options(parsed_args,
class AddRole(command.Command):
- """Adds a role to a user or group on a domain or project"""
+ """Adds a role assignment to a user or group on a domain or project"""
def get_parser(self, prog_name):
parser = super(AddRole, self).get_parser(prog_name)
@@ -115,6 +119,7 @@ class AddRole(command.Command):
help=_('Role to add to <user> (name or ID)'),
)
_add_identity_and_resource_options_to_parser(parser)
+ common.add_role_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
@@ -123,9 +128,15 @@ class AddRole(command.Command):
if (not parsed_args.user and not parsed_args.domain
and not parsed_args.group and not parsed_args.project):
return
+
+ domain_id = None
+ if parsed_args.role_domain:
+ domain_id = common.find_domain(identity_client,
+ parsed_args.role_domain).id
role = utils.find_resource(
identity_client.roles,
parsed_args.role,
+ domain_id=domain_id
)
kwargs = _process_identity_and_resource_options(
@@ -150,6 +161,11 @@ class CreateRole(command.ShowOne):
help=_('New role name'),
)
parser.add_argument(
+ '--domain',
+ metavar='<domain>',
+ help=_('Domain the role belongs to (name or ID)'),
+ )
+ parser.add_argument(
'--or-show',
action='store_true',
help=_('Return existing role'),
@@ -159,15 +175,23 @@ class CreateRole(command.ShowOne):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
+ domain_id = None
+ if parsed_args.domain:
+ domain_id = common.find_domain(identity_client,
+ parsed_args.domain).id
+
try:
- role = identity_client.roles.create(name=parsed_args.name)
- except ks_exc.Conflict as e:
+ role = identity_client.roles.create(
+ name=parsed_args.name, domain=domain_id)
+
+ except ks_exc.Conflict:
if parsed_args.or_show:
role = utils.find_resource(identity_client.roles,
- parsed_args.name)
- self.log.info(_('Returning existing role %s'), role.name)
+ parsed_args.name,
+ domain_id=domain_id)
+ LOG.info(_('Returning existing role %s'), role.name)
else:
- raise e
+ raise
role._info.pop('links')
return zip(*sorted(six.iteritems(role._info)))
@@ -184,15 +208,26 @@ class DeleteRole(command.Command):
nargs="+",
help=_('Role(s) to delete (name or ID)'),
)
+ parser.add_argument(
+ '--domain',
+ metavar='<domain>',
+ help=_('Domain the role belongs to (name or ID)'),
+ )
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
+ domain_id = None
+ if parsed_args.domain:
+ domain_id = common.find_domain(identity_client,
+ parsed_args.domain).id
+
for role in parsed_args.roles:
role_obj = utils.find_resource(
identity_client.roles,
role,
+ domain_id=domain_id
)
identity_client.roles.delete(role_obj.id)
@@ -202,6 +237,18 @@ class ListRole(command.Lister):
def get_parser(self, prog_name):
parser = super(ListRole, self).get_parser(prog_name)
+
+ # TODO(henry-nash): The use of the List Role command to list
+ # assignments (as well as roles) has been deprecated. In order
+ # to support domain specific roles, we are overriding the domain
+ # option to allow specification of the domain for the role. This does
+ # not conflict with any existing commands, since for the deprecated
+ # assignments listing you were never allowed to only specify a domain
+ # (you also needed to specify a user).
+ #
+ # Once we have removed the deprecated options entirely, we must
+ # replace the call to _add_identity_and_resource_options_to_parser()
+ # below with just adding the domain option into the parser.
_add_identity_and_resource_options_to_parser(parser)
return parser
@@ -235,8 +282,14 @@ class ListRole(command.Lister):
# no user or group specified, list all roles in the system
if not parsed_args.user and not parsed_args.group:
- columns = ('ID', 'Name')
- data = identity_client.roles.list()
+ if not parsed_args.domain:
+ columns = ('ID', 'Name')
+ data = identity_client.roles.list()
+ else:
+ columns = ('ID', 'Name', 'Domain')
+ data = identity_client.roles.list(domain_id=domain.id)
+ for role in data:
+ role.domain = domain.name
elif parsed_args.user and parsed_args.domain:
columns = ('ID', 'Name', 'Domain', 'User')
data = identity_client.roles.list(
@@ -247,6 +300,10 @@ class ListRole(command.Lister):
for user_role in data:
user_role.user = user.name
user_role.domain = domain.name
+ self.log.warning(_('Listing assignments using role list is '
+ 'deprecated. Use role assignment list --user '
+ '<user-name> --domain <domain-name> --names '
+ 'instead.'))
elif parsed_args.user and parsed_args.project:
columns = ('ID', 'Name', 'Project', 'User')
data = identity_client.roles.list(
@@ -257,6 +314,10 @@ class ListRole(command.Lister):
for user_role in data:
user_role.user = user.name
user_role.project = project.name
+ self.log.warning(_('Listing assignments using role list is '
+ 'deprecated. Use role assignment list --user '
+ '<user-name> --project <project-name> --names '
+ 'instead.'))
elif parsed_args.user:
columns = ('ID', 'Name')
data = identity_client.roles.list(
@@ -264,6 +325,10 @@ class ListRole(command.Lister):
domain='default',
os_inherit_extension_inherited=parsed_args.inherited
)
+ self.log.warning(_('Listing assignments using role list is '
+ 'deprecated. Use role assignment list --user '
+ '<user-name> --domain default --names '
+ 'instead.'))
elif parsed_args.group and parsed_args.domain:
columns = ('ID', 'Name', 'Domain', 'Group')
data = identity_client.roles.list(
@@ -274,6 +339,10 @@ class ListRole(command.Lister):
for group_role in data:
group_role.group = group.name
group_role.domain = domain.name
+ self.log.warning(_('Listing assignments using role list is '
+ 'deprecated. Use role assignment list --group '
+ '<group-name> --domain <domain-name> --names '
+ 'instead.'))
elif parsed_args.group and parsed_args.project:
columns = ('ID', 'Name', 'Project', 'Group')
data = identity_client.roles.list(
@@ -284,6 +353,10 @@ class ListRole(command.Lister):
for group_role in data:
group_role.group = group.name
group_role.project = project.name
+ self.log.warning(_('Listing assignments using role list is '
+ 'deprecated. Use role assignment list --group '
+ '<group-name> --project <project-name> --names '
+ 'instead.'))
else:
sys.stderr.write(_("Error: If a user or group is specified, "
"either --domain or --project must also be "
@@ -298,7 +371,7 @@ class ListRole(command.Lister):
class RemoveRole(command.Command):
- """Remove role from domain/project : user/group"""
+ """Removes a role assignment from domain/project : user/group"""
def get_parser(self, prog_name):
parser = super(RemoveRole, self).get_parser(prog_name)
@@ -308,6 +381,8 @@ class RemoveRole(command.Command):
help=_('Role to remove (name or ID)'),
)
_add_identity_and_resource_options_to_parser(parser)
+ common.add_role_domain_option_to_parser(parser)
+
return parser
def take_action(self, parsed_args):
@@ -318,9 +393,15 @@ class RemoveRole(command.Command):
sys.stderr.write(_("Incorrect set of arguments provided. "
"See openstack --help for more details\n"))
return
+
+ domain_id = None
+ if parsed_args.role_domain:
+ domain_id = common.find_domain(identity_client,
+ parsed_args.role_domain).id
role = utils.find_resource(
identity_client.roles,
parsed_args.role,
+ domain_id=domain_id
)
kwargs = _process_identity_and_resource_options(
@@ -344,6 +425,11 @@ class SetRole(command.Command):
help=_('Role to modify (name or ID)'),
)
parser.add_argument(
+ '--domain',
+ metavar='<domain>',
+ help=_('Domain the role belongs to (name or ID)'),
+ )
+ parser.add_argument(
'--name',
metavar='<name>',
help=_('Set role name'),
@@ -353,14 +439,14 @@ class SetRole(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- if not parsed_args.name:
- sys.stderr.write(_("Incorrect set of arguments provided. "
- "See openstack --help for more details\n"))
- return
- role = utils.find_resource(
- identity_client.roles,
- parsed_args.role,
- )
+ domain_id = None
+ if parsed_args.domain:
+ domain_id = common.find_domain(identity_client,
+ parsed_args.domain).id
+
+ role = utils.find_resource(identity_client.roles,
+ parsed_args.role,
+ domain_id=domain_id)
identity_client.roles.update(role.id, name=parsed_args.name)
@@ -375,15 +461,24 @@ class ShowRole(command.ShowOne):
metavar='<role>',
help=_('Role to display (name or ID)'),
)
+ parser.add_argument(
+ '--domain',
+ metavar='<domain>',
+ help=_('Domain the role belongs to (name or ID)'),
+ )
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- role = utils.find_resource(
- identity_client.roles,
- parsed_args.role,
- )
+ domain_id = None
+ if parsed_args.domain:
+ domain_id = common.find_domain(identity_client,
+ parsed_args.domain).id
+
+ role = utils.find_resource(identity_client.roles,
+ parsed_args.role,
+ domain_id=domain_id)
role._info.pop('links')
return zip(*sorted(six.iteritems(role._info)))
diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py
index 521075f..d25cc6c 100644
--- a/openstackclient/identity/v3/role_assignment.py
+++ b/openstackclient/identity/v3/role_assignment.py
@@ -13,8 +13,9 @@
"""Identity v3 Assignment action implementations """
-from openstackclient.common import command
-from openstackclient.common import utils
+from osc_lib.command import command
+from osc_lib import utils
+
from openstackclient.i18n import _
from openstackclient.identity import common
@@ -35,6 +36,7 @@ class ListRoleAssignment(command.Lister):
metavar='<role>',
help=_('Role to filter (name or ID)'),
)
+ common.add_role_domain_option_to_parser(parser)
parser.add_argument(
'--names',
action="store_true",
@@ -66,6 +68,19 @@ class ListRoleAssignment(command.Lister):
)
common.add_project_domain_option_to_parser(parser)
common.add_inherited_option_to_parser(parser)
+ parser.add_argument(
+ '--auth-user',
+ action="store_true",
+ dest='authuser',
+ help='Only list assignments for the authenticated user',
+ )
+ parser.add_argument(
+ '--auth-project',
+ action="store_true",
+ dest='authproject',
+ help='Only list assignments for the project to which the '
+ 'authenticated user\'s token is scoped',
+ )
return parser
def _as_tuple(self, assignment):
@@ -74,12 +89,18 @@ class ListRoleAssignment(command.Lister):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
+ auth_ref = self.app.client_manager.auth_ref
role = None
+ role_domain_id = None
+ if parsed_args.role_domain:
+ role_domain_id = common.find_domain(identity_client,
+ parsed_args.role_domain).id
if parsed_args.role:
role = utils.find_resource(
identity_client.roles,
parsed_args.role,
+ domain_id=role_domain_id
)
user = None
@@ -89,6 +110,12 @@ class ListRoleAssignment(command.Lister):
parsed_args.user,
parsed_args.user_domain,
)
+ elif parsed_args.authuser:
+ if auth_ref:
+ user = common.find_user(
+ identity_client,
+ auth_ref.user_id
+ )
domain = None
if parsed_args.domain:
@@ -104,6 +131,12 @@ class ListRoleAssignment(command.Lister):
parsed_args.project,
parsed_args.project_domain,
)
+ elif parsed_args.authproject:
+ if auth_ref:
+ project = common.find_project(
+ identity_client,
+ auth_ref.project_id
+ )
group = None
if parsed_args.group:
@@ -178,6 +211,12 @@ class ListRoleAssignment(command.Lister):
if hasattr(assignment, 'role'):
if include_names:
+ # TODO(henry-nash): If this is a domain specific role it
+ # would be good show this as role@domain, although this
+ # domain info is not yet included in the response from the
+ # server. Although we could get it by re-reading the role
+ # from the ID, let's wait until the server does the right
+ # thing.
setattr(assignment, 'role', assignment.role['name'])
else:
setattr(assignment, 'role', assignment.role['id'])
diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py
index 35507a6..97e64dc 100644
--- a/openstackclient/identity/v3/service.py
+++ b/openstackclient/identity/v3/service.py
@@ -15,15 +15,20 @@
"""Identity v3 Service action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-import sys
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
class CreateService(command.ShowOne):
"""Create new service"""
@@ -76,23 +81,36 @@ class CreateService(command.ShowOne):
class DeleteService(command.Command):
- """Delete service"""
+ """Delete service(s)"""
def get_parser(self, prog_name):
parser = super(DeleteService, self).get_parser(prog_name)
parser.add_argument(
'service',
metavar='<service>',
- help=_('Service to delete (type, name or ID)'),
+ nargs='+',
+ help=_('Service(s) to delete (type, name or ID)'),
)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
-
- service = common.find_service(identity_client, parsed_args.service)
-
- identity_client.services.delete(service.id)
+ result = 0
+ for i in parsed_args.service:
+ try:
+ service = common.find_service(identity_client, i)
+ identity_client.services.delete(service.id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete consumer with type, "
+ "name or ID '%(service)s': %(e)s")
+ % {'service': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.service)
+ msg = (_("%(result)s of %(total)s services failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListService(command.Lister):
@@ -129,7 +147,7 @@ class SetService(command.Command):
parser.add_argument(
'service',
metavar='<service>',
- help=_('Service to update (type, name or ID)'),
+ help=_('Service to modify (type, name or ID)'),
)
parser.add_argument(
'--type',
@@ -162,14 +180,6 @@ class SetService(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- if (not parsed_args.name
- and not parsed_args.type
- and not parsed_args.description
- and not parsed_args.enable
- and not parsed_args.disable):
- sys.stderr.write(_("Incorrect set of arguments provided. "
- "See openstack --help for more details\n"))
- return
service = common.find_service(identity_client,
parsed_args.service)
kwargs = {}
diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py
index f1e9f35..8548ae1 100644
--- a/openstackclient/identity/v3/service_provider.py
+++ b/openstackclient/identity/v3/service_provider.py
@@ -13,14 +13,19 @@
"""Service Provider action implementations"""
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-import sys
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateServiceProvider(command.ShowOne):
"""Create new service provider"""
@@ -82,21 +87,35 @@ class CreateServiceProvider(command.ShowOne):
class DeleteServiceProvider(command.Command):
- """Delete service provider"""
+ """Delete service provider(s)"""
def get_parser(self, prog_name):
parser = super(DeleteServiceProvider, self).get_parser(prog_name)
parser.add_argument(
'service_provider',
metavar='<service-provider>',
- help=_('Service provider to delete'),
+ nargs='+',
+ help=_('Service provider(s) to delete'),
)
return parser
def take_action(self, parsed_args):
service_client = self.app.client_manager.identity
- service_client.federation.service_providers.delete(
- parsed_args.service_provider)
+ result = 0
+ for i in parsed_args.service_provider:
+ try:
+ service_client.federation.service_providers.delete(i)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete service provider with "
+ "name or ID '%(provider)s': %(e)s")
+ % {'provider': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.service_provider)
+ msg = (_("%(result)s of %(total)s service providers failed"
+ " to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListServiceProvider(command.Lister):
@@ -163,19 +182,13 @@ class SetServiceProvider(command.Command):
elif parsed_args.disable is True:
enabled = False
- if not any((enabled is not None, parsed_args.description,
- parsed_args.service_provider_url,
- parsed_args.auth_url)):
- sys.stdout.write(_("Service Provider not updated, no arguments "
- "present\n"))
- return (None, None)
-
- service_provider = federation_client.service_providers.update(
- parsed_args.service_provider, enabled=enabled,
+ federation_client.service_providers.update(
+ parsed_args.service_provider,
+ enabled=enabled,
description=parsed_args.description,
auth_url=parsed_args.auth_url,
- sp_url=parsed_args.service_provider_url)
- return zip(*sorted(six.iteritems(service_provider._info)))
+ sp_url=parsed_args.service_provider_url,
+ )
class ShowServiceProvider(command.ShowOne):
diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py
index 56a7497..ecf0969 100644
--- a/openstackclient/identity/v3/token.py
+++ b/openstackclient/identity/v3/token.py
@@ -15,11 +15,11 @@
"""Identity v3 Token action implementations"""
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common
@@ -174,13 +174,23 @@ class IssueToken(command.ShowOne):
return parser
def take_action(self, parsed_args):
- if not self.app.client_manager.auth_ref:
+ auth_ref = self.app.client_manager.auth_ref
+ if not auth_ref:
raise exceptions.AuthorizationFailure(
_("Only an authorized user may issue a new token."))
- token = self.app.client_manager.auth_ref.service_catalog.get_token()
- if 'tenant_id' in token:
- token['project_id'] = token.pop('tenant_id')
- return zip(*sorted(six.iteritems(token)))
+
+ data = {}
+ if auth_ref.auth_token:
+ data['id'] = auth_ref.auth_token
+ if auth_ref.expires:
+ data['expires'] = auth_ref.expires
+ if auth_ref.project_id:
+ data['project_id'] = auth_ref.project_id
+ if auth_ref.user_id:
+ data['user_id'] = auth_ref.user_id
+ if auth_ref.domain_id:
+ data['domain_id'] = auth_ref.domain_id
+ return zip(*sorted(six.iteritems(data)))
class RevokeToken(command.Command):
diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py
index 336b703..bbc86ad 100644
--- a/openstackclient/identity/v3/trust.py
+++ b/openstackclient/identity/v3/trust.py
@@ -14,10 +14,11 @@
"""Identity v3 Trust action implementations"""
import datetime
+
+from osc_lib.command import command
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common
diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py
index 5cb8e48..f116174 100644
--- a/openstackclient/identity/v3/unscoped_saml.py
+++ b/openstackclient/identity/v3/unscoped_saml.py
@@ -17,9 +17,10 @@ The first step of federated auth is to fetch an unscoped token. From there,
the user can list domains and projects they are allowed to access, and request
a scoped token."""
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import utils
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.i18n import _
diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py
index a71b4b7..dc47ef8 100644
--- a/openstackclient/identity/v3/user.py
+++ b/openstackclient/identity/v3/user.py
@@ -16,17 +16,20 @@
"""Identity v3 User action implementations"""
import copy
-import six
-import sys
+import logging
from keystoneauth1 import exceptions as ks_exc
+from osc_lib.command import command
+from osc_lib import utils
+import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
class CreateUser(command.ShowOne):
"""Create new user"""
@@ -117,14 +120,14 @@ class CreateUser(command.ShowOne):
description=parsed_args.description,
enabled=enabled
)
- except ks_exc.Conflict as e:
+ except ks_exc.Conflict:
if parsed_args.or_show:
user = utils.find_resource(identity_client.users,
parsed_args.name,
domain_id=domain_id)
- self.log.info(_('Returning existing user %s'), user.name)
+ LOG.info(_('Returning existing user %s'), user.name)
else:
- raise e
+ raise
user._info.pop('links')
return zip(*sorted(six.iteritems(user._info)))
@@ -273,7 +276,7 @@ class SetUser(command.Command):
parser.add_argument(
'user',
metavar='<user>',
- help=_('User to change (name or ID)'),
+ help=_('User to modify (name or ID)'),
)
parser.add_argument(
'--name',
@@ -326,18 +329,6 @@ class SetUser(command.Command):
if parsed_args.password_prompt:
parsed_args.password = utils.get_password(self.app.stdin)
- if (not parsed_args.name
- and not parsed_args.name
- and not parsed_args.password
- and not parsed_args.email
- and not parsed_args.project
- and not parsed_args.description
- and not parsed_args.enable
- and not parsed_args.disable):
- sys.stderr.write(_("Incorrect set of arguments provided. "
- "See openstack --help for more details\n"))
- return
-
user = utils.find_resource(
identity_client.users,
parsed_args.user,
@@ -440,14 +431,16 @@ class ShowUser(command.ShowOne):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
+ user_str = common._get_token_resource(identity_client, 'user',
+ parsed_args.user)
if parsed_args.domain:
domain = common.find_domain(identity_client, parsed_args.domain)
user = utils.find_resource(identity_client.users,
- parsed_args.user,
+ user_str,
domain_id=domain.id)
else:
user = utils.find_resource(identity_client.users,
- parsed_args.user)
+ user_str)
user._info.pop('links')
return zip(*sorted(six.iteritems(user._info)))
diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py
index 9c45a63..1be6c76 100644
--- a/openstackclient/image/client.py
+++ b/openstackclient/image/client.py
@@ -15,7 +15,8 @@
import logging
-from openstackclient.common import utils
+from osc_lib import utils
+
from openstackclient.i18n import _
@@ -46,15 +47,15 @@ def make_client(instance):
endpoint = instance.get_endpoint_for_service_type(
API_NAME,
- region_name=instance._region_name,
- interface=instance._interface,
+ region_name=instance.region_name,
+ interface=instance.interface,
)
client = image_client(
endpoint,
token=instance.auth.get_token(instance.session),
- cacert=instance._cacert,
- insecure=instance._insecure,
+ cacert=instance.cacert,
+ insecure=not instance.verify,
)
# Create the low-level API
@@ -69,8 +70,8 @@ def make_client(instance):
session=instance.session,
endpoint=instance.get_endpoint_for_service_type(
IMAGE_API_TYPE,
- region_name=instance._region_name,
- interface=instance._interface,
+ region_name=instance.region_name,
+ interface=instance.interface,
)
)
diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py
index e2109fc..5f669c6 100644
--- a/openstackclient/image/v1/image.py
+++ b/openstackclient/image/v1/image.py
@@ -17,8 +17,8 @@
import argparse
import io
+import logging
import os
-import six
import sys
if os.name == "nt":
@@ -27,10 +27,12 @@ else:
msvcrt = None
from glanceclient.common import utils as gc_utils
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import utils
+import six
+
from openstackclient.api import utils as api_utils
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
@@ -38,6 +40,9 @@ DEFAULT_CONTAINER_FORMAT = 'bare'
DEFAULT_DISK_FORMAT = 'raw'
+LOG = logging.getLogger(__name__)
+
+
def _format_visibility(data):
"""Return a formatted visibility string
@@ -188,10 +193,8 @@ class CreateImage(command.ShowOne):
image_client = self.app.client_manager.image
if getattr(parsed_args, 'owner', None) is not None:
- self.log.warning(_(
- 'The --owner option is deprecated, '
- 'please use --project instead.'
- ))
+ LOG.warning(_('The --owner option is deprecated, '
+ 'please use --project instead.'))
# Build an attribute dict from the parsed args, only include
# attributes that were actually set on the command line
@@ -375,6 +378,7 @@ class ListImage(command.Lister):
'Disk Format',
'Container Format',
'Size',
+ 'Checksum',
'Status',
'is_public',
'protected',
@@ -387,6 +391,7 @@ class ListImage(command.Lister):
'Disk Format',
'Container Format',
'Size',
+ 'Checksum',
'Status',
'Visibility',
'Protected',
@@ -607,10 +612,8 @@ class SetImage(command.Command):
image_client = self.app.client_manager.image
if getattr(parsed_args, 'owner', None) is not None:
- self.log.warning(_(
- 'The --owner option is deprecated, '
- 'please use --project instead.'
- ))
+ LOG.warning(_('The --owner option is deprecated, '
+ 'please use --project instead.'))
kwargs = {}
copy_attrs = ('name', 'owner', 'min_disk', 'min_ram', 'properties',
@@ -683,18 +686,13 @@ class SetImage(command.Command):
# will do a chunked transfer
kwargs["data"] = sys.stdin
else:
- self.log.warning(_('Use --stdin to enable read '
- 'image data from standard '
- 'input'))
+ LOG.warning(_('Use --stdin to enable read image '
+ 'data from standard input'))
if image.properties and parsed_args.properties:
image.properties.update(kwargs['properties'])
kwargs['properties'] = image.properties
- if not kwargs:
- self.log.warning('no arguments specified')
- return
-
image = image_client.images.update(image.id, **kwargs)
finally:
# Clean up open files - make sure data isn't a string
diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py
index fa1de42..0712e09 100644
--- a/openstackclient/image/v2/image.py
+++ b/openstackclient/image/v2/image.py
@@ -16,15 +16,16 @@
"""Image V2 Action Implementations"""
import argparse
-import six
+import logging
from glanceclient.common import utils as gc_utils
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+import six
from openstackclient.api import utils as api_utils
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common
@@ -33,6 +34,9 @@ DEFAULT_CONTAINER_FORMAT = 'bare'
DEFAULT_DISK_FORMAT = 'raw'
+LOG = logging.getLogger(__name__)
+
+
def _format_image(image):
"""Format an image to make it more consistent with OSC operations. """
@@ -280,10 +284,8 @@ class CreateImage(command.ShowOne):
project_arg = parsed_args.project
if parsed_args.owner:
project_arg = parsed_args.owner
- self.log.warning(_(
- 'The --owner option is deprecated, '
- 'please use --project instead.'
- ))
+ LOG.warning(_('The --owner option is deprecated, '
+ 'please use --project instead.'))
if project_arg:
kwargs['owner'] = common.find_project(
identity_client,
@@ -301,7 +303,7 @@ class CreateImage(command.ShowOne):
"the same time"))
if fp is None and parsed_args.file:
- self.log.warning(_("Failed to get an image file."))
+ LOG.warning(_("Failed to get an image file."))
return {}, {}
if parsed_args.owner:
@@ -337,7 +339,7 @@ class CreateImage(command.ShowOne):
with fp:
try:
image_client.images.upload(image.id, fp)
- except Exception as e:
+ except Exception:
# If the upload fails for some reason attempt to remove the
# dangling queued image made by the create() call above but
# only if the user did not specify an id which indicates
@@ -347,7 +349,7 @@ class CreateImage(command.ShowOne):
image_client.images.delete(image.id)
except Exception:
pass # we don't care about this one
- raise e # now, throw the upload exception again
+ raise # now, throw the upload exception again
# update the image after the data has been uploaded
image = image_client.images.get(image.id)
@@ -372,13 +374,27 @@ class DeleteImage(command.Command):
return parser
def take_action(self, parsed_args):
+
+ del_result = 0
image_client = self.app.client_manager.image
for image in parsed_args.images:
- image_obj = utils.find_resource(
- image_client.images,
- image,
- )
- image_client.images.delete(image_obj.id)
+ try:
+ image_obj = utils.find_resource(
+ image_client.images,
+ image,
+ )
+ image_client.images.delete(image_obj.id)
+ except Exception as e:
+ del_result += 1
+ LOG.error(_("Failed to delete image with name or "
+ "ID '%(image)s': %(e)s"),
+ {'image': image, 'e': e})
+
+ total = len(parsed_args.images)
+ if (del_result > 0):
+ msg = (_("Failed to delete %(dresult)s of %(total)s images.")
+ % {'dresult': del_result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListImage(command.Lister):
@@ -474,6 +490,7 @@ class ListImage(command.Lister):
'Disk Format',
'Container Format',
'Size',
+ 'Checksum',
'Status',
'visibility',
'protected',
@@ -486,6 +503,7 @@ class ListImage(command.Lister):
'Disk Format',
'Container Format',
'Size',
+ 'Checksum',
'Status',
'Visibility',
'Protected',
@@ -792,10 +810,8 @@ class SetImage(command.Command):
project_arg = parsed_args.project
if parsed_args.owner:
project_arg = parsed_args.owner
- self.log.warning(_(
- 'The --owner option is deprecated, '
- 'please use --project instead.'
- ))
+ LOG.warning(_('The --owner option is deprecated, '
+ 'please use --project instead.'))
if project_arg:
kwargs['owner'] = common.find_project(
identity_client,
@@ -803,11 +819,6 @@ class SetImage(command.Command):
parsed_args.project_domain,
).id
- # Checks if anything that requires getting the image
- if not (kwargs or parsed_args.deactivate or parsed_args.activate):
- msg = _("No arguments specified")
- raise exceptions.CommandError(msg)
-
image = utils.find_resource(
image_client.images, parsed_args.image)
@@ -819,20 +830,17 @@ class SetImage(command.Command):
image_client.images.reactivate(image.id)
activation_status = "activated"
- # Check if need to do the actual update
- if not kwargs:
- return {}, {}
-
if parsed_args.tags:
# Tags should be extended, but duplicates removed
kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags)))
try:
image = image_client.images.update(image.id, **kwargs)
- except Exception as e:
+ except Exception:
if activation_status is not None:
- print("Image %s was %s." % (image.id, activation_status))
- raise e
+ LOG.info(_("Image %(id)s was %(status)s."),
+ {'id': image.id, 'status': activation_status})
+ raise
class ShowImage(command.ShowOne):
@@ -895,10 +903,6 @@ class UnsetImage(command.Command):
parsed_args.image,
)
- if not (parsed_args.tags or parsed_args.properties):
- msg = _("No arguments specified")
- raise exceptions.CommandError(msg)
-
kwargs = {}
tagret = 0
propret = 0
@@ -907,8 +911,8 @@ class UnsetImage(command.Command):
try:
image_client.image_tags.delete(image.id, k)
except Exception:
- self.log.error(_("tag unset failed,"
- " '%s' is a nonexistent tag ") % k)
+ LOG.error(_("tag unset failed, '%s' is a "
+ "nonexistent tag "), k)
tagret += 1
if parsed_args.properties:
@@ -916,8 +920,8 @@ class UnsetImage(command.Command):
try:
assert(k in image.keys())
except AssertionError:
- self.log.error(_("property unset failed,"
- " '%s' is a nonexistent property ") % k)
+ LOG.error(_("property unset failed, '%s' is a "
+ "nonexistent property "), k)
propret += 1
image_client.images.update(
image.id,
diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py
index d711f4f..c562058 100644
--- a/openstackclient/network/client.py
+++ b/openstackclient/network/client.py
@@ -15,8 +15,8 @@ import logging
from openstack import connection
from openstack import profile
+from osc_lib import utils
-from openstackclient.common import utils
from openstackclient.i18n import _
@@ -34,8 +34,9 @@ API_VERSIONS = {
def make_client(instance):
"""Returns a network proxy"""
prof = profile.Profile()
- prof.set_region(API_NAME, instance._region_name)
+ prof.set_region(API_NAME, instance.region_name)
prof.set_version(API_NAME, instance._api_version[API_NAME])
+ prof.set_interface(API_NAME, instance.interface)
conn = connection.Connection(authenticator=instance.session.auth,
verify=instance.session.verify,
cert=instance.session.cert,
diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py
index 4028ac0..2b1a565 100644
--- a/openstackclient/network/common.py
+++ b/openstackclient/network/common.py
@@ -12,13 +12,18 @@
#
import abc
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
import six
-from openstackclient.common import command
-from openstackclient.common import exceptions
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
@six.add_metaclass(abc.ABCMeta)
class NetworkAndComputeCommand(command.Command):
"""Network and Compute Command
@@ -38,10 +43,10 @@ class NetworkAndComputeCommand(command.Command):
parsed_args)
def get_parser(self, prog_name):
- self.log.debug('get_parser(%s)', prog_name)
+ LOG.debug('get_parser(%s)', prog_name)
parser = super(NetworkAndComputeCommand, self).get_parser(prog_name)
parser = self.update_parser_common(parser)
- self.log.debug('common parser: %s', parser)
+ LOG.debug('common parser: %s', parser)
if self.app.client_manager.is_network_endpoint_enabled():
return self.update_parser_network(parser)
else:
@@ -101,12 +106,12 @@ class NetworkAndComputeDelete(NetworkAndComputeCommand):
"name_or_id": r,
"e": e,
}
- self.app.log.error(msg)
+ LOG.error(msg)
ret += 1
if ret:
total = len(resources)
- msg = _("%(num)s of %(total)s %(resource)s failed to delete.") % {
+ msg = _("%(num)s of %(total)s %(resource)ss failed to delete.") % {
"num": ret,
"total": total,
"resource": self.resource,
@@ -133,10 +138,10 @@ class NetworkAndComputeLister(command.Lister):
parsed_args)
def get_parser(self, prog_name):
- self.log.debug('get_parser(%s)', prog_name)
+ LOG.debug('get_parser(%s)', prog_name)
parser = super(NetworkAndComputeLister, self).get_parser(prog_name)
parser = self.update_parser_common(parser)
- self.log.debug('common parser: %s', parser)
+ LOG.debug('common parser: %s', parser)
if self.app.client_manager.is_network_endpoint_enabled():
return self.update_parser_network(parser)
else:
@@ -184,10 +189,10 @@ class NetworkAndComputeShowOne(command.ShowOne):
parsed_args)
def get_parser(self, prog_name):
- self.log.debug('get_parser(%s)', prog_name)
+ LOG.debug('get_parser(%s)', prog_name)
parser = super(NetworkAndComputeShowOne, self).get_parser(prog_name)
parser = self.update_parser_common(parser)
- self.log.debug('common parser: %s', parser)
+ LOG.debug('common parser: %s', parser)
if self.app.client_manager.is_network_endpoint_enabled():
return self.update_parser_network(parser)
else:
diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py
index dbc6865..6cd13f8 100644
--- a/openstackclient/network/v2/address_scope.py
+++ b/openstackclient/network/v2/address_scope.py
@@ -13,13 +13,19 @@
"""Address scope action implementations"""
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import utils
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+LOG = logging.getLogger(__name__)
+
+
def _get_columns(item):
columns = list(item.keys())
if 'tenant_id' in columns:
@@ -94,7 +100,7 @@ class CreateAddressScope(command.ShowOne):
columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters={})
- return columns, data
+ return (columns, data)
class DeleteAddressScope(command.Command):
@@ -121,9 +127,9 @@ class DeleteAddressScope(command.Command):
client.delete_address_scope(obj)
except Exception as e:
result += 1
- self.app.log.error(_("Failed to delete address scope with "
- "name or ID '%(scope)s': %(e)s")
- % {'scope': scope, 'e': e})
+ LOG.error(_("Failed to delete address scope with "
+ "name or ID '%(scope)s': %(e)s"),
+ {'scope': scope, 'e': e})
if result > 0:
total = len(parsed_args.address_scope)
@@ -200,9 +206,6 @@ class SetAddressScope(command.Command):
attrs['shared'] = True
if parsed_args.no_share:
attrs['shared'] = False
- if attrs == {}:
- msg = _("Nothing specified to be set.")
- raise exceptions.CommandError(msg)
client.update_address_scope(obj, **attrs)
@@ -227,4 +230,4 @@ class ShowAddressScope(command.ShowOne):
columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters={})
- return columns, data
+ return (columns, data)
diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py
index 21f8659..454335f 100644
--- a/openstackclient/network/v2/floating_ip.py
+++ b/openstackclient/network/v2/floating_ip.py
@@ -13,7 +13,10 @@
"""IP Floating action implementations"""
-from openstackclient.common import utils
+import logging
+
+from osc_lib import utils
+
from openstackclient.i18n import _
from openstackclient.network import common
@@ -30,25 +33,26 @@ def _get_attrs(client_manager, parsed_args):
attrs = {}
network_client = client_manager.network
+ # Name of a network could be empty string.
if parsed_args.network is not None:
network = network_client.find_network(parsed_args.network,
ignore_missing=False)
attrs['floating_network_id'] = network.id
- if parsed_args.subnet is not None:
+ if parsed_args.subnet:
subnet = network_client.find_subnet(parsed_args.subnet,
ignore_missing=False)
attrs['subnet_id'] = subnet.id
- if parsed_args.port is not None:
+ if parsed_args.port:
port = network_client.find_port(parsed_args.port,
ignore_missing=False)
attrs['port_id'] = port.id
- if parsed_args.floating_ip_address is not None:
+ if parsed_args.floating_ip_address:
attrs['floating_ip_address'] = parsed_args.floating_ip_address
- if parsed_args.fixed_ip_address is not None:
+ if parsed_args.fixed_ip_address:
attrs['fixed_ip_address'] = parsed_args.fixed_ip_address
return attrs
@@ -109,29 +113,79 @@ class CreateFloatingIP(common.NetworkAndComputeShowOne):
return (columns, data)
-class DeleteFloatingIP(common.NetworkAndComputeCommand):
- """Delete floating IP"""
+class CreateIPFloating(CreateFloatingIP):
+ """Create floating IP"""
+
+ # TODO(tangchen): Remove this class and ``ip floating create`` command
+ # two cycles after Mitaka.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action_network(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip create" instead.'))
+ return super(CreateIPFloating, self).take_action_network(
+ client, parsed_args)
+
+ def take_action_compute(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip create" instead.'))
+ return super(CreateIPFloating, self).take_action_compute(
+ client, parsed_args)
+
+
+class DeleteFloatingIP(common.NetworkAndComputeDelete):
+ """Delete floating IP(s)"""
+
+ # Used by base class to find resources in parsed_args.
+ resource = 'floating_ip'
+ r = None
def update_parser_common(self, parser):
parser.add_argument(
'floating_ip',
metavar="<floating-ip>",
- help=_("Floating IP to delete (IP address or ID)")
+ nargs="+",
+ help=_("Floating IP(s) to delete (IP address or ID)")
)
return parser
def take_action_network(self, client, parsed_args):
- obj = client.find_ip(parsed_args.floating_ip)
+ obj = client.find_ip(self.r, ignore_missing=False)
client.delete_ip(obj)
def take_action_compute(self, client, parsed_args):
- obj = utils.find_resource(
- client.floating_ips,
- parsed_args.floating_ip,
- )
+ obj = utils.find_resource(client.floating_ips, self.r)
client.floating_ips.delete(obj.id)
+class DeleteIPFloating(DeleteFloatingIP):
+ """Delete floating IP(s)"""
+
+ # TODO(tangchen): Remove this class and ``ip floating delete`` command
+ # two cycles after Mitaka.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action_network(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip delete" instead.'))
+ return super(DeleteIPFloating, self).take_action_network(
+ client, parsed_args)
+
+ def take_action_compute(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip delete" instead.'))
+ return super(DeleteIPFloating, self).take_action_compute(
+ client, parsed_args)
+
+
class ListFloatingIP(common.NetworkAndComputeLister):
"""List floating IP(s)"""
@@ -183,8 +237,32 @@ class ListFloatingIP(common.NetworkAndComputeLister):
) for s in data))
+class ListIPFloating(ListFloatingIP):
+ """List floating IP(s)"""
+
+ # TODO(tangchen): Remove this class and ``ip floating list`` command
+ # two cycles after Mitaka.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action_network(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip list" instead.'))
+ return super(ListIPFloating, self).take_action_network(
+ client, parsed_args)
+
+ def take_action_compute(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip list" instead.'))
+ return super(ListIPFloating, self).take_action_compute(
+ client, parsed_args)
+
+
class ShowFloatingIP(common.NetworkAndComputeShowOne):
- """Show floating IP details"""
+ """Display floating IP details"""
def update_parser_common(self, parser):
parser.add_argument(
@@ -208,3 +286,27 @@ class ShowFloatingIP(common.NetworkAndComputeShowOne):
columns = _get_columns(obj._info)
data = utils.get_dict_properties(obj._info, columns)
return (columns, data)
+
+
+class ShowIPFloating(ShowFloatingIP):
+ """Display floating IP details"""
+
+ # TODO(tangchen): Remove this class and ``ip floating show`` command
+ # two cycles after Mitaka.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action_network(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip show" instead.'))
+ return super(ShowIPFloating, self).take_action_network(
+ client, parsed_args)
+
+ def take_action_compute(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip show" instead.'))
+ return super(ShowIPFloating, self).take_action_compute(
+ client, parsed_args)
diff --git a/openstackclient/network/v2/floating_ip_pool.py b/openstackclient/network/v2/floating_ip_pool.py
new file mode 100644
index 0000000..c78ca06
--- /dev/null
+++ b/openstackclient/network/v2/floating_ip_pool.py
@@ -0,0 +1,66 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Floating IP Pool action implementations"""
+
+import logging
+
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.i18n import _
+from openstackclient.network import common
+
+
+class ListFloatingIPPool(common.NetworkAndComputeLister):
+ """List pools of floating IP addresses"""
+
+ def take_action_network(self, client, parsed_args):
+ msg = _("Floating ip pool operations are only available for "
+ "Compute v2 network.")
+ raise exceptions.CommandError(msg)
+
+ def take_action_compute(self, client, parsed_args):
+ columns = (
+ 'Name',
+ )
+ data = client.floating_ip_pools.list()
+
+ return (columns,
+ (utils.get_item_properties(
+ s, columns,
+ ) for s in data))
+
+
+class ListIPFloatingPool(ListFloatingIPPool):
+ """List pools of floating IP addresses"""
+
+ # TODO(tangchen): Remove this class and ``ip floating pool list`` command
+ # two cycles after Mitaka.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action_network(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip pool list" instead.'))
+ return super(ListIPFloatingPool, self).take_action_network(
+ client, parsed_args)
+
+ def take_action_compute(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip pool list" instead.'))
+ return super(ListIPFloatingPool, self).take_action_compute(
+ client, parsed_args)
diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py
index cc24033..1d7b2ae 100644
--- a/openstackclient/network/v2/ip_availability.py
+++ b/openstackclient/network/v2/ip_availability.py
@@ -13,8 +13,9 @@
"""IP Availability Info implementations"""
-from openstackclient.common import command
-from openstackclient.common import utils
+from osc_lib.command import command
+from osc_lib import utils
+
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
@@ -40,15 +41,17 @@ class ListIPAvailability(command.Lister):
parser.add_argument(
'--ip-version',
type=int,
+ default=4,
choices=[4, 6],
metavar='<ip-version>',
dest='ip_version',
- help=_("List IP availability of given IP version networks"),
+ help=_("List IP availability of given IP version "
+ "networks (default is 4)"),
)
parser.add_argument(
'--project',
metavar='<project>',
- help=_("List IP availability of given project"),
+ help=_("List IP availability of given project (name or ID)"),
)
identity_common.add_project_domain_option_to_parser(parser)
return parser
@@ -106,4 +109,4 @@ class ShowIPAvailability(command.ShowOne):
ignore_missing=False)
columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return columns, data
+ return (columns, data)
diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py
index bf01e2e..ccc02fd 100644
--- a/openstackclient/network/v2/network.py
+++ b/openstackclient/network/v2/network.py
@@ -13,9 +13,9 @@
"""Network action implementations"""
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import utils
+from osc_lib.command import command
+from osc_lib import utils
+
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
from openstackclient.network import common
@@ -58,6 +58,10 @@ def _get_attrs(client_manager, parsed_args):
attrs['shared'] = True
if parsed_args.no_share:
attrs['shared'] = False
+ if parsed_args.enable_port_security:
+ attrs['port_security_enabled'] = True
+ if parsed_args.disable_port_security:
+ attrs['port_security_enabled'] = False
# "network set" command doesn't support setting project.
if 'project' in parsed_args and parsed_args.project is not None:
@@ -104,11 +108,11 @@ def _add_additional_network_options(parser):
parser.add_argument(
'--provider-network-type',
metavar='<provider-network-type>',
- choices=['flat', 'gre', 'local',
+ choices=['flat', 'geneve', 'gre', 'local',
'vlan', 'vxlan'],
help=_("The physical mechanism by which the virtual network "
"is implemented. The supported options are: "
- "flat, gre, local, vlan, vxlan"))
+ "flat, geneve, gre, local, vlan, vxlan."))
parser.add_argument(
'--provider-physical-network',
metavar='<provider-physical-network>',
@@ -119,8 +123,8 @@ def _add_additional_network_options(parser):
'--provider-segment',
metavar='<provider-segment>',
dest='segmentation_id',
- help=_("VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN "
- "networks"))
+ help=_("VLAN ID for VLAN networks or Tunnel ID for "
+ "GENEVE/GRE/VXLAN networks"))
vlan_transparent_grp = parser.add_mutually_exclusive_group()
vlan_transparent_grp.add_argument(
@@ -197,6 +201,19 @@ class CreateNetwork(common.NetworkAndComputeShowOne):
"(Network Availability Zone extension required, "
"repeat option to set multiple availability zones)")
)
+ port_security_group = parser.add_mutually_exclusive_group()
+ port_security_group.add_argument(
+ '--enable-port-security',
+ action='store_true',
+ help=_("Enable port security by default for ports created on "
+ "this network (default)")
+ )
+ port_security_group.add_argument(
+ '--disable-port-security',
+ action='store_true',
+ help=_("Disable port security by default for ports created on "
+ "this network")
+ )
external_router_grp = parser.add_mutually_exclusive_group()
external_router_grp.add_argument(
'--external',
@@ -403,6 +420,19 @@ class SetNetwork(command.Command):
action='store_true',
help=_("Do not share the network between projects")
)
+ port_security_group = parser.add_mutually_exclusive_group()
+ port_security_group.add_argument(
+ '--enable-port-security',
+ action='store_true',
+ help=_("Enable port security by default for ports created on "
+ "this network")
+ )
+ port_security_group.add_argument(
+ '--disable-port-security',
+ action='store_true',
+ help=_("Disable port security by default for ports created on "
+ "this network")
+ )
external_router_grp = parser.add_mutually_exclusive_group()
external_router_grp.add_argument(
'--external',
@@ -434,10 +464,6 @@ class SetNetwork(command.Command):
obj = client.find_network(parsed_args.network, ignore_missing=False)
attrs = _get_attrs(self.app.client_manager, parsed_args)
- if attrs == {}:
- msg = _("Nothing specified to be set")
- raise exceptions.CommandError(msg)
-
client.update_network(obj, **attrs)
diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py
new file mode 100644
index 0000000..fdb34bb
--- /dev/null
+++ b/openstackclient/network/v2/network_agent.py
@@ -0,0 +1,160 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Network agent action implementations"""
+
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.i18n import _
+
+
+LOG = logging.getLogger(__name__)
+
+
+def _format_admin_state(state):
+ return 'UP' if state else 'DOWN'
+
+
+_formatters = {
+ 'admin_state_up': _format_admin_state,
+ 'configurations': utils.format_dict,
+}
+
+
+class DeleteNetworkAgent(command.Command):
+ """Delete network agent(s)"""
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteNetworkAgent, self).get_parser(prog_name)
+ parser.add_argument(
+ 'network_agent',
+ metavar="<network-agent>",
+ nargs='+',
+ help=(_("Network agent(s) to delete (ID only)"))
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+
+ for agent in parsed_args.network_agent:
+ try:
+ obj = client.get_agent(agent, ignore_missing=False)
+ client.delete_agent(obj)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete network agent with "
+ "ID '%(agent)s': %(e)s"),
+ {'agent': agent, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.network_agent)
+ msg = (_("%(result)s of %(total)s network agents failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListNetworkAgent(command.Lister):
+ """List network agents"""
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ columns = (
+ 'id',
+ 'agent_type',
+ 'host',
+ 'availability_zone',
+ 'alive',
+ 'admin_state_up',
+ 'binary'
+ )
+ column_headers = (
+ 'ID',
+ 'Agent Type',
+ 'Host',
+ 'Availability Zone',
+ 'Alive',
+ 'State',
+ 'Binary'
+ )
+ data = client.agents()
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns, formatters=_formatters,
+ ) for s in data))
+
+
+class SetNetworkAgent(command.Command):
+ """Set network agent properties"""
+
+ def get_parser(self, prog_name):
+ parser = super(SetNetworkAgent, self).get_parser(prog_name)
+ parser.add_argument(
+ 'network_agent',
+ metavar="<network-agent>",
+ help=(_("Network agent to modify (ID only)"))
+ )
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_("Set network agent description")
+ )
+ admin_group = parser.add_mutually_exclusive_group()
+ admin_group.add_argument(
+ '--enable',
+ action='store_true',
+ help=_("Enable network agent")
+ )
+ admin_group.add_argument(
+ '--disable',
+ action='store_true',
+ help=_("Disable network agent")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.get_agent(parsed_args.network_agent, ignore_missing=False)
+ attrs = {}
+ if parsed_args.description is not None:
+ attrs['description'] = str(parsed_args.description)
+ if parsed_args.enable:
+ attrs['admin_state_up'] = True
+ if parsed_args.disable:
+ attrs['admin_state_up'] = False
+ client.update_agent(obj, **attrs)
+
+
+class ShowNetworkAgent(command.ShowOne):
+ """Display network agent details"""
+
+ def get_parser(self, prog_name):
+ parser = super(ShowNetworkAgent, self).get_parser(prog_name)
+ parser.add_argument(
+ 'network_agent',
+ metavar="<network-agent>",
+ help=(_("Network agent to display (ID only)"))
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.get_agent(parsed_args.network_agent, ignore_missing=False)
+ columns = tuple(sorted(list(obj.keys())))
+ data = utils.get_item_properties(obj, columns, formatters=_formatters,)
+ return columns, data
diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py
new file mode 100644
index 0000000..bb29579
--- /dev/null
+++ b/openstackclient/network/v2/network_rbac.py
@@ -0,0 +1,248 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""RBAC action implementations"""
+
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.i18n import _
+from openstackclient.identity import common as identity_common
+
+
+LOG = logging.getLogger(__name__)
+
+
+def _get_columns(item):
+ columns = list(item.keys())
+ if 'tenant_id' in columns:
+ columns.remove('tenant_id')
+ columns.append('project_id')
+ if 'target_tenant' in columns:
+ columns.remove('target_tenant')
+ columns.append('target_project_id')
+ return tuple(sorted(columns))
+
+
+def _get_attrs(client_manager, parsed_args):
+ attrs = {}
+ attrs['object_type'] = parsed_args.type
+ attrs['action'] = parsed_args.action
+
+ network_client = client_manager.network
+ if parsed_args.type == 'network':
+ object_id = network_client.find_network(
+ parsed_args.rbac_object, ignore_missing=False).id
+ if parsed_args.type == 'qos_policy':
+ # TODO(Huanxuan Ao): Support finding a object ID by obejct name
+ # after qos policy finding supported in SDK.
+ object_id = parsed_args.rbac_object
+ attrs['object_id'] = object_id
+
+ identity_client = client_manager.identity
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.target_project,
+ parsed_args.target_project_domain,
+ ).id
+ attrs['target_tenant'] = project_id
+ if parsed_args.project is not None:
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ attrs['tenant_id'] = project_id
+
+ return attrs
+
+
+class CreateNetworkRBAC(command.ShowOne):
+ """Create network RBAC policy"""
+
+ def get_parser(self, prog_name):
+ parser = super(CreateNetworkRBAC, self).get_parser(prog_name)
+ parser.add_argument(
+ 'rbac_object',
+ metavar="<rbac-object>",
+ help=_("The object to which this RBAC policy affects (name or "
+ "ID for network objects, ID only for QoS policy objects)")
+ )
+ parser.add_argument(
+ '--type',
+ metavar="<type>",
+ required=True,
+ choices=['qos_policy', 'network'],
+ help=_('Type of the object that RBAC policy '
+ 'affects ("qos_policy" or "network")')
+ )
+ parser.add_argument(
+ '--action',
+ metavar="<action>",
+ required=True,
+ choices=['access_as_external', 'access_as_shared'],
+ help=_('Action for the RBAC policy '
+ '("access_as_external" or "access_as_shared")')
+ )
+ parser.add_argument(
+ '--target-project',
+ required=True,
+ metavar="<target-project>",
+ help=_('The project to which the RBAC policy '
+ 'will be enforced (name or ID)')
+ )
+ parser.add_argument(
+ '--target-project-domain',
+ metavar='<target-project-domain>',
+ help=_('Domain the target project belongs to (name or ID). '
+ 'This can be used in case collisions between project names '
+ 'exist.'),
+ )
+ parser.add_argument(
+ '--project',
+ metavar="<project>",
+ help=_('The owner project (name or ID)')
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_rbac_policy(**attrs)
+ columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns)
+ return columns, data
+
+
+class DeleteNetworkRBAC(command.Command):
+ """Delete network RBAC policy(s)"""
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteNetworkRBAC, self).get_parser(prog_name)
+ parser.add_argument(
+ 'rbac_policy',
+ metavar="<rbac-policy>",
+ nargs='+',
+ help=_("RBAC policy(s) to delete (ID only)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ result = 0
+
+ for rbac in parsed_args.rbac_policy:
+ try:
+ obj = client.find_rbac_policy(rbac, ignore_missing=False)
+ client.delete_rbac_policy(obj)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete RBAC policy with "
+ "ID '%(rbac)s': %(e)s"),
+ {'rbac': rbac, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.rbac_policy)
+ msg = (_("%(result)s of %(total)s RBAC policies failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListNetworkRBAC(command.Lister):
+ """List network RBAC policies"""
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+
+ columns = (
+ 'id',
+ 'object_type',
+ 'object_id',
+ )
+ column_headers = (
+ 'ID',
+ 'Object Type',
+ 'Object ID',
+ )
+
+ data = client.rbac_policies()
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ ) for s in data))
+
+
+class SetNetworkRBAC(command.Command):
+ """Set network RBAC policy properties"""
+
+ def get_parser(self, prog_name):
+ parser = super(SetNetworkRBAC, self).get_parser(prog_name)
+ parser.add_argument(
+ 'rbac_policy',
+ metavar="<rbac-policy>",
+ help=_("RBAC policy to be modified (ID only)")
+ )
+ parser.add_argument(
+ '--target-project',
+ metavar="<target-project>",
+ help=_('The project to which the RBAC policy '
+ 'will be enforced (name or ID)')
+ )
+ parser.add_argument(
+ '--target-project-domain',
+ metavar='<target-project-domain>',
+ help=_('Domain the target project belongs to (name or ID). '
+ 'This can be used in case collisions between project names '
+ 'exist.'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_rbac_policy(parsed_args.rbac_policy,
+ ignore_missing=False)
+ attrs = {}
+ if parsed_args.target_project:
+ identity_client = self.app.client_manager.identity
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.target_project,
+ parsed_args.target_project_domain,
+ ).id
+ attrs['target_tenant'] = project_id
+ client.update_rbac_policy(obj, **attrs)
+
+
+class ShowNetworkRBAC(command.ShowOne):
+ """Display network RBAC policy details"""
+
+ def get_parser(self, prog_name):
+ parser = super(ShowNetworkRBAC, self).get_parser(prog_name)
+ parser.add_argument(
+ 'rbac_policy',
+ metavar="<rbac-policy>",
+ help=_("RBAC policy (ID only)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_rbac_policy(parsed_args.rbac_policy,
+ ignore_missing=False)
+ columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns)
+ return columns, data
diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py
index 818ffc0..bedf15f 100644
--- a/openstackclient/network/v2/network_segment.py
+++ b/openstackclient/network/v2/network_segment.py
@@ -15,8 +15,9 @@
# TODO(rtheis): Add description and name properties when support is available.
-from openstackclient.common import command
-from openstackclient.common import utils
+from osc_lib.command import command
+from osc_lib import utils
+
from openstackclient.i18n import _
diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py
index 7ef3964..05c5a01 100644
--- a/openstackclient/network/v2/port.py
+++ b/openstackclient/network/v2/port.py
@@ -14,12 +14,15 @@
"""Port action implementations"""
import argparse
+import copy
+import json
import logging
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import parseractions
-from openstackclient.common import utils
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
@@ -62,6 +65,32 @@ def _get_columns(item):
return tuple(sorted(columns))
+class JSONKeyValueAction(argparse.Action):
+ """A custom action to parse arguments as JSON or key=value pairs
+
+ Ensures that ``dest`` is a dict
+ """
+
+ def __call__(self, parser, namespace, values, option_string=None):
+
+ # Make sure we have an empty dict rather than None
+ if getattr(namespace, self.dest, None) is None:
+ setattr(namespace, self.dest, {})
+
+ # Try to load JSON first before falling back to <key>=<value>.
+ current_dest = getattr(namespace, self.dest)
+ try:
+ current_dest.update(json.loads(values))
+ except ValueError as e:
+ if '=' in values:
+ current_dest.update([values.split('=', 1)])
+ else:
+ msg = _("Expected '<key>=<value>' or JSON data for option "
+ "%(option)s, but encountered JSON parsing error: "
+ "%(error)s") % {"option": option_string, "error": e}
+ raise argparse.ArgumentTypeError(msg)
+
+
def _get_attrs(client_manager, parsed_args):
attrs = {}
@@ -168,7 +197,8 @@ def _add_updatable_args(parser):
parser.add_argument(
'--device-owner',
metavar='<device-owner>',
- help=_("Device owner of this port")
+ help=_("Device owner of this port. This is the entity that uses "
+ "the port (for example, network:dhcp).")
)
parser.add_argument(
'--vnic-type',
@@ -218,9 +248,9 @@ class CreatePort(command.ShowOne):
parser.add_argument(
'--binding-profile',
metavar='<binding-profile>',
- action=parseractions.KeyValueAction,
- help=_("Custom data to be passed as binding:profile: "
- "<key>=<value> "
+ action=JSONKeyValueAction,
+ help=_("Custom data to be passed as binding:profile. Data may "
+ "be passed as <key>=<value> or JSON. "
"(repeat option to set multiple binding:profile data)")
)
admin_group = parser.add_mutually_exclusive_group()
@@ -266,7 +296,7 @@ class CreatePort(command.ShowOne):
columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return columns, data
+ return (columns, data)
class DeletePort(command.Command):
@@ -292,9 +322,9 @@ class DeletePort(command.Command):
client.delete_port(obj)
except Exception as e:
result += 1
- self.app.log.error(_("Failed to delete port with "
- "name or ID '%(port)s': %(e)s")
- % {'port': port, 'e': e})
+ LOG.error(_("Failed to delete port with "
+ "name or ID '%(port)s': %(e)s"),
+ {'port': port, 'e': e})
if result > 0:
total = len(parsed_args.port)
@@ -309,6 +339,13 @@ class ListPort(command.Lister):
def get_parser(self, prog_name):
parser = super(ListPort, self).get_parser(prog_name)
parser.add_argument(
+ '--device-owner',
+ metavar='<device-owner>',
+ help=_("List only ports with the specified device owner. "
+ "This is the entity that uses the port (for example, "
+ "network:dhcp).")
+ )
+ parser.add_argument(
'--router',
metavar='<router>',
dest='router',
@@ -333,10 +370,12 @@ class ListPort(command.Lister):
)
filters = {}
+ if parsed_args.device_owner is not None:
+ filters['device_owner'] = parsed_args.device_owner
if parsed_args.router:
_router = client.find_router(parsed_args.router,
ignore_missing=False)
- filters = {'device_id': _router.id}
+ filters['device_id'] = _router.id
data = client.ports(**filters)
@@ -389,9 +428,9 @@ class SetPort(command.Command):
binding_profile.add_argument(
'--binding-profile',
metavar='<binding-profile>',
- action=parseractions.KeyValueAction,
- help=_("Custom data to be passed as binding:profile: "
- "<key>=<value> "
+ action=JSONKeyValueAction,
+ help=_("Custom data to be passed as binding:profile. Data may "
+ "be passed as <key>=<value> or JSON. "
"(repeat option to set multiple binding:profile data)")
)
binding_profile.add_argument(
@@ -426,9 +465,6 @@ class SetPort(command.Command):
elif parsed_args.no_fixed_ip:
attrs['fixed_ips'] = []
- if attrs == {}:
- msg = _("Nothing specified to be set")
- raise exceptions.CommandError(msg)
client.update_port(obj, **attrs)
@@ -449,4 +485,62 @@ class ShowPort(command.ShowOne):
obj = client.find_port(parsed_args.port, ignore_missing=False)
columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return columns, data
+ return (columns, data)
+
+
+class UnsetPort(command.Command):
+ """Unset port properties"""
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetPort, self).get_parser(prog_name)
+ parser.add_argument(
+ '--fixed-ip',
+ metavar='subnet=<subnet>,ip-address=<ip-address>',
+ action=parseractions.MultiKeyValueAction,
+ optional_keys=['subnet', 'ip-address'],
+ help=_("Desired IP and/or subnet (name or ID) which should be "
+ "removed from this port: subnet=<subnet>,"
+ "ip-address=<ip-address> (repeat option to unset multiple "
+ "fixed IP addresses)"))
+
+ parser.add_argument(
+ '--binding-profile',
+ metavar='<binding-profile-key>',
+ action='append',
+ help=_("Desired key which should be removed from binding:profile"
+ "(repeat option to unset multiple binding:profile data)"))
+ parser.add_argument(
+ 'port',
+ metavar="<port>",
+ help=_("Port to modify (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_port(parsed_args.port, ignore_missing=False)
+ # SDK ignores update() if it recieves a modified obj and attrs
+ # To handle the same tmp_obj is created in all take_action of
+ # Unset* classes
+ tmp_fixed_ips = copy.deepcopy(obj.fixed_ips)
+ tmp_binding_profile = copy.deepcopy(obj.binding_profile)
+ _prepare_fixed_ips(self.app.client_manager, parsed_args)
+ attrs = {}
+ if parsed_args.fixed_ip:
+ try:
+ for ip in parsed_args.fixed_ip:
+ tmp_fixed_ips.remove(ip)
+ except ValueError:
+ msg = _("Port does not contain fixed-ip %s") % ip
+ raise exceptions.CommandError(msg)
+ attrs['fixed_ips'] = tmp_fixed_ips
+ if parsed_args.binding_profile:
+ try:
+ for key in parsed_args.binding_profile:
+ del tmp_binding_profile[key]
+ except KeyError:
+ msg = _("Port does not contain binding-profile %s") % key
+ raise exceptions.CommandError(msg)
+ attrs['binding:profile'] = tmp_binding_profile
+ if attrs:
+ client.update_port(obj, **attrs)
diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py
index a2f0df1..f6d96d0 100644
--- a/openstackclient/network/v2/router.py
+++ b/openstackclient/network/v2/router.py
@@ -14,13 +14,15 @@
"""Router action implementations"""
import argparse
+import copy
import json
import logging
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import parseractions
-from openstackclient.common import utils
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
@@ -204,7 +206,7 @@ class CreateRouter(command.ShowOne):
columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return columns, data
+ return (columns, data)
class DeleteRouter(command.Command):
@@ -222,9 +224,23 @@ class DeleteRouter(command.Command):
def take_action(self, parsed_args):
client = self.app.client_manager.network
+ result = 0
+
for router in parsed_args.router:
- obj = client.find_router(router)
- client.delete_router(obj)
+ try:
+ obj = client.find_router(router, ignore_missing=False)
+ client.delete_router(obj)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete router with "
+ "name or ID '%(router)s': %(e)s")
+ % {'router': router, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.router)
+ msg = (_("%(result)s of %(total)s routers failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListRouter(command.Lister):
@@ -294,7 +310,7 @@ class RemovePortFromRouter(command.Command):
parser.add_argument(
'port',
metavar='<port>',
- help=_("Port to be removed (name or ID)")
+ help=_("Port to be removed and deleted (name or ID)")
)
return parser
@@ -426,10 +442,6 @@ class SetRouter(command.Command):
route['nexthop'] = route.pop('gateway')
attrs['routes'] = obj.routes + parsed_args.routes
- if attrs == {}:
- msg = _("Nothing specified to be set")
- raise exceptions.CommandError(msg)
-
client.update_router(obj, **attrs)
@@ -450,4 +462,46 @@ class ShowRouter(command.ShowOne):
obj = client.find_router(parsed_args.router, ignore_missing=False)
columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
- return columns, data
+ return (columns, data)
+
+
+class UnsetRouter(command.Command):
+ """Unset router properties"""
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetRouter, self).get_parser(prog_name)
+ parser.add_argument(
+ '--route',
+ metavar='destination=<subnet>,gateway=<ip-address>',
+ action=parseractions.MultiKeyValueAction,
+ dest='routes',
+ default=None,
+ required_keys=['destination', 'gateway'],
+ help=_("Routes to be removed from the router "
+ "destination: destination subnet (in CIDR notation) "
+ "gateway: nexthop IP address "
+ "(repeat option to unset multiple routes)"))
+ parser.add_argument(
+ 'router',
+ metavar="<router>",
+ help=_("Router to modify (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_router(parsed_args.router, ignore_missing=False)
+ tmp_routes = copy.deepcopy(obj.routes)
+ attrs = {}
+ if parsed_args.routes:
+ try:
+ for route in parsed_args.routes:
+ tmp_routes.remove(route)
+ except ValueError:
+ msg = (_("Router does not contain route %s") % route)
+ raise exceptions.CommandError(msg)
+ for route in tmp_routes:
+ route['nexthop'] = route.pop('gateway')
+ attrs['routes'] = tmp_routes
+ if attrs:
+ client.update_router(obj, **attrs)
diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py
index 1ef2754..f832f72 100644
--- a/openstackclient/network/v2/security_group.py
+++ b/openstackclient/network/v2/security_group.py
@@ -14,9 +14,10 @@
"""Security Group action implementations"""
import argparse
+
+from osc_lib import utils
import six
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
from openstackclient.network import common
@@ -163,26 +164,28 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne):
return (display_columns, data)
-class DeleteSecurityGroup(common.NetworkAndComputeCommand):
- """Delete a security group"""
+class DeleteSecurityGroup(common.NetworkAndComputeDelete):
+ """Delete security group(s)"""
+
+ # Used by base class to find resources in parsed_args.
+ resource = 'group'
+ r = None
def update_parser_common(self, parser):
parser.add_argument(
'group',
metavar='<group>',
- help=_("Security group to delete (name or ID)")
+ nargs="+",
+ help=_("Security group(s) to delete (name or ID)"),
)
return parser
def take_action_network(self, client, parsed_args):
- obj = client.find_security_group(parsed_args.group)
+ obj = client.find_security_group(self.r, ignore_missing=False)
client.delete_security_group(obj)
def take_action_compute(self, client, parsed_args):
- data = utils.find_resource(
- client.security_groups,
- parsed_args.group,
- )
+ data = utils.find_resource(client.security_groups, self.r)
client.security_groups.delete(data.id)
diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py
index 5abe9b9..e3be44e 100644
--- a/openstackclient/network/v2/security_group_rule.py
+++ b/openstackclient/network/v2/security_group_rule.py
@@ -14,16 +14,17 @@
"""Security Group Rule action implementations"""
import argparse
-import six
try:
from novaclient.v2 import security_group_rules as compute_secgroup_rules
except ImportError:
from novaclient.v1_1 import security_group_rules as compute_secgroup_rules
-from openstackclient.common import exceptions
-from openstackclient.common import parseractions
-from openstackclient.common import utils
+from osc_lib.cli import parseractions
+from osc_lib import exceptions
+from osc_lib import utils
+import six
+
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
from openstackclient.network import common
@@ -332,23 +333,29 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
return _format_security_group_rule_show(obj._info)
-class DeleteSecurityGroupRule(common.NetworkAndComputeCommand):
- """Delete a security group rule"""
+class DeleteSecurityGroupRule(common.NetworkAndComputeDelete):
+ """Delete security group rule(s)"""
+
+ # Used by base class to find resources in parsed_args.
+ resource = 'rule'
+ r = None
def update_parser_common(self, parser):
parser.add_argument(
'rule',
metavar='<rule>',
- help=_("Security group rule to delete (ID only)")
+ nargs="+",
+ help=_("Security group rule(s) to delete (ID only)")
)
return parser
def take_action_network(self, client, parsed_args):
- obj = client.find_security_group_rule(parsed_args.rule)
+ obj = client.find_security_group_rule(
+ self.r, ignore_missing=False)
client.delete_security_group_rule(obj)
def take_action_compute(self, client, parsed_args):
- client.security_group_rules.delete(parsed_args.rule)
+ client.security_group_rules.delete(self.r)
class ListSecurityGroupRule(common.NetworkAndComputeLister):
@@ -519,8 +526,8 @@ class ShowSecurityGroupRule(common.NetworkAndComputeShowOne):
break
if obj is None:
- msg = _("Could not find security group rule with ID ") + \
- parsed_args.rule
+ msg = _("Could not find security group rule "
+ "with ID '%s'") % parsed_args.rule
raise exceptions.CommandError(msg)
# NOTE(rtheis): Format security group rule
diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py
index e7e1be9..2245280 100644
--- a/openstackclient/network/v2/subnet.py
+++ b/openstackclient/network/v2/subnet.py
@@ -12,16 +12,27 @@
#
"""Subnet action implementations"""
+
import copy
+import logging
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+LOG = logging.getLogger(__name__)
+
+
+def _update_arguments(obj_list, parsed_args_list):
+ for item in parsed_args_list:
+ obj_list.remove(item)
+
+
def _format_allocation_pools(data):
pool_formatted = ['%s-%s' % (pool.get('start', ''), pool.get('end', ''))
for pool in data]
@@ -136,6 +147,9 @@ def _get_attrs(client_manager, parsed_args, is_create=True):
attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode
if parsed_args.ipv6_address_mode is not None:
attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode
+ if 'network_segment' in parsed_args:
+ attrs['segment_id'] = client.find_segment(
+ parsed_args.network_segment, ignore_missing=False).id
if 'gateway' in parsed_args and parsed_args.gateway is not None:
gateway = parsed_args.gateway.lower()
@@ -154,7 +168,7 @@ def _get_attrs(client_manager, parsed_args, is_create=True):
attrs['allocation_pools'] = parsed_args.allocation_pools
if parsed_args.dhcp:
attrs['enable_dhcp'] = True
- elif parsed_args.no_dhcp:
+ if parsed_args.no_dhcp:
attrs['enable_dhcp'] = False
if ('dns_nameservers' in parsed_args and
parsed_args.dns_nameservers is not None):
@@ -209,7 +223,6 @@ class CreateSubnet(command.ShowOne):
dhcp_enable_group.add_argument(
'--dhcp',
action='store_true',
- default=True,
help=_("Enable DHCP (default)")
)
dhcp_enable_group.add_argument(
@@ -226,7 +239,7 @@ class CreateSubnet(command.ShowOne):
"'auto': Gateway address should automatically be chosen "
"from within the subnet itself, 'none': This subnet will "
"not use a gateway, e.g.: --gateway 192.168.9.1, "
- "--gateway auto, --gateway none (default is 'auto')")
+ "--gateway auto, --gateway none (default is 'auto').")
)
parser.add_argument(
'--ip-version',
@@ -235,7 +248,7 @@ class CreateSubnet(command.ShowOne):
choices=[4, 6],
help=_("IP version (default is 4). Note that when subnet pool is "
"specified, IP version is determined from the subnet pool "
- "and this option is ignored")
+ "and this option is ignored.")
)
parser.add_argument(
'--ipv6-ra-mode',
@@ -249,6 +262,13 @@ class CreateSubnet(command.ShowOne):
help=_("IPv6 address mode, "
"valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]")
)
+ if self.app.options.os_beta_command:
+ parser.add_argument(
+ '--network-segment',
+ metavar='<network-segment>',
+ help=_("Network segment to associate with this subnet "
+ "(ID only)")
+ )
parser.add_argument(
'--network',
required=True,
@@ -268,21 +288,37 @@ class CreateSubnet(command.ShowOne):
class DeleteSubnet(command.Command):
- """Delete subnet"""
+ """Delete subnet(s)"""
def get_parser(self, prog_name):
parser = super(DeleteSubnet, self).get_parser(prog_name)
parser.add_argument(
'subnet',
metavar="<subnet>",
- help=_("Subnet to delete (name or ID)")
+ nargs='+',
+ help=_("Subnet(s) to delete (name or ID)")
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.network
- client.delete_subnet(
- client.find_subnet(parsed_args.subnet))
+ result = 0
+
+ for subnet in parsed_args.subnet:
+ try:
+ obj = client.find_subnet(subnet, ignore_missing=False)
+ client.delete_subnet(obj)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete subnet with "
+ "name or ID '%(subnet)s': %(e)s")
+ % {'subnet': subnet, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.subnet)
+ msg = (_("%(result)s of %(total)s subnets failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListSubnet(command.Lister):
@@ -302,15 +338,30 @@ class ListSubnet(command.Lister):
choices=[4, 6],
metavar='<ip-version>',
dest='ip_version',
- help=_("List only subnets of given IP version in output"
+ help=_("List only subnets of given IP version in output."
"Allowed values for IP version are 4 and 6."),
)
+ dhcp_enable_group = parser.add_mutually_exclusive_group()
+ dhcp_enable_group.add_argument(
+ '--dhcp',
+ action='store_true',
+ help=_("List subnets which have DHCP enabled")
+ )
+ dhcp_enable_group.add_argument(
+ '--no-dhcp',
+ action='store_true',
+ help=_("List subnets which have DHCP disabled")
+ )
return parser
def take_action(self, parsed_args):
filters = {}
if parsed_args.ip_version:
filters['ip_version'] = parsed_args.ip_version
+ if parsed_args.dhcp:
+ filters['enable_dhcp'] = True
+ elif parsed_args.no_dhcp:
+ filters['enable_dhcp'] = False
data = self.app.client_manager.network.subnets(**filters)
headers = ('ID', 'Name', 'Network', 'Subnet')
@@ -363,7 +414,7 @@ class SetSubnet(command.Command):
help=_("Specify a gateway for the subnet. The options are: "
"<ip-address>: Specific IP address to use as the gateway, "
"'none': This subnet will not use a gateway, "
- "e.g.: --gateway 192.168.9.1, --gateway none")
+ "e.g.: --gateway 192.168.9.1, --gateway none.")
)
_get_common_parse_arguments(parser)
return parser
@@ -373,9 +424,6 @@ class SetSubnet(command.Command):
obj = client.find_subnet(parsed_args.subnet, ignore_missing=False)
attrs = _get_attrs(self.app.client_manager, parsed_args,
is_create=False)
- if not attrs:
- msg = "Nothing specified to be set"
- raise exceptions.CommandError(msg)
if 'dns_nameservers' in attrs:
attrs['dns_nameservers'] += obj.dns_nameservers
if 'host_routes' in attrs:
@@ -404,3 +452,81 @@ class ShowSubnet(command.ShowOne):
columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
return (columns, data)
+
+
+class UnsetSubnet(command.Command):
+ """Unset subnet properties"""
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetSubnet, self).get_parser(prog_name)
+ parser.add_argument(
+ '--allocation-pool',
+ metavar='start=<ip-address>,end=<ip-address>',
+ dest='allocation_pools',
+ action=parseractions.MultiKeyValueAction,
+ required_keys=['start', 'end'],
+ help=_('Allocation pool to be removed from this subnet '
+ 'e.g.: start=192.168.199.2,end=192.168.199.254 '
+ '(repeat option to unset multiple Allocation pools)')
+ )
+ parser.add_argument(
+ '--dns-nameserver',
+ metavar='<dns-nameserver>',
+ action='append',
+ dest='dns_nameservers',
+ help=_('DNS server to be removed from this subnet '
+ '(repeat option to set multiple DNS servers)')
+ )
+ parser.add_argument(
+ '--host-route',
+ metavar='destination=<subnet>,gateway=<ip-address>',
+ dest='host_routes',
+ action=parseractions.MultiKeyValueAction,
+ required_keys=['destination', 'gateway'],
+ help=_('Route to be removed from this subnet '
+ 'e.g.: destination=10.10.0.0/16,gateway=192.168.71.254 '
+ 'destination: destination subnet (in CIDR notation) '
+ 'gateway: nexthop IP address '
+ '(repeat option to unset multiple host routes)')
+ )
+ parser.add_argument(
+ 'subnet',
+ metavar="<subnet>",
+ help=_("Subnet to modify (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_subnet(parsed_args.subnet, ignore_missing=False)
+ tmp_obj = copy.deepcopy(obj)
+ attrs = {}
+ if parsed_args.dns_nameservers:
+ try:
+ _update_arguments(tmp_obj.dns_nameservers,
+ parsed_args.dns_nameservers)
+ except ValueError as error:
+ msg = (_("%s not in dns-nameservers") % str(error))
+ raise exceptions.CommandError(msg)
+ attrs['dns_nameservers'] = tmp_obj.dns_nameservers
+ if parsed_args.host_routes:
+ try:
+ _update_arguments(
+ tmp_obj.host_routes,
+ convert_entries_to_nexthop(parsed_args.host_routes))
+ except ValueError as error:
+ msg = (_("Subnet does not have %s in host-routes") %
+ str(error))
+ raise exceptions.CommandError(msg)
+ attrs['host_routes'] = tmp_obj.host_routes
+ if parsed_args.allocation_pools:
+ try:
+ _update_arguments(tmp_obj.allocation_pools,
+ parsed_args.allocation_pools)
+ except ValueError as error:
+ msg = (_("Subnet does not have %s in allocation-pools") %
+ str(error))
+ raise exceptions.CommandError(msg)
+ attrs['allocation_pools'] = tmp_obj.allocation_pools
+ if attrs:
+ client.update_subnet(obj, **attrs)
diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py
index a1a9442..d3fab8a 100644
--- a/openstackclient/network/v2/subnet_pool.py
+++ b/openstackclient/network/v2/subnet_pool.py
@@ -12,15 +12,21 @@
#
"""Subnet pool action implementations"""
+import copy
+import logging
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+LOG = logging.getLogger(__name__)
+
+
def _get_columns(item):
columns = list(item.keys())
if 'tenant_id' in columns:
@@ -176,21 +182,37 @@ class CreateSubnetPool(command.ShowOne):
class DeleteSubnetPool(command.Command):
- """Delete subnet pool"""
+ """Delete subnet pool(s)"""
def get_parser(self, prog_name):
parser = super(DeleteSubnetPool, self).get_parser(prog_name)
parser.add_argument(
'subnet_pool',
metavar='<subnet-pool>',
- help=_("Subnet pool to delete (name or ID)")
+ nargs='+',
+ help=_("Subnet pool(s) to delete (name or ID)")
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.network
- obj = client.find_subnet_pool(parsed_args.subnet_pool)
- client.delete_subnet_pool(obj)
+ result = 0
+
+ for pool in parsed_args.subnet_pool:
+ try:
+ obj = client.find_subnet_pool(pool, ignore_missing=False)
+ client.delete_subnet_pool(obj)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete subnet pool with "
+ "name or ID '%(pool)s': %(e)s")
+ % {'pool': pool, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.subnet_pool)
+ msg = (_("%(result)s of %(total)s subnet pools failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListSubnetPool(command.Lister):
@@ -286,9 +308,6 @@ class SetSubnetPool(command.Command):
ignore_missing=False)
attrs = _get_attrs(self.app.client_manager, parsed_args)
- if attrs == {}:
- msg = _("Nothing specified to be set")
- raise exceptions.CommandError(msg)
# Existing prefixes must be a subset of the new prefixes.
if 'prefixes' in attrs:
@@ -318,3 +337,43 @@ class ShowSubnetPool(command.ShowOne):
columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
return (columns, data)
+
+
+class UnsetSubnetPool(command.Command):
+ """Unset subnet pool properties"""
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetSubnetPool, self).get_parser(prog_name)
+ parser.add_argument(
+ '--pool-prefix',
+ metavar='<pool-prefix>',
+ action='append',
+ dest='prefixes',
+ help=_('Remove subnet pool prefixes (in CIDR notation). '
+ '(repeat option to unset multiple prefixes).'),
+ )
+ parser.add_argument(
+ 'subnet_pool',
+ metavar="<subnet-pool>",
+ help=_("Subnet pool to modify (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_subnet_pool(
+ parsed_args.subnet_pool, ignore_missing=False)
+ tmp_prefixes = copy.deepcopy(obj.prefixes)
+ attrs = {}
+ if parsed_args.prefixes:
+ for prefix in parsed_args.prefixes:
+ try:
+ tmp_prefixes.remove(prefix)
+ except ValueError:
+ msg = _(
+ "Subnet pool does not "
+ "contain prefix %s") % prefix
+ raise exceptions.CommandError(msg)
+ attrs['prefixes'] = tmp_prefixes
+ if attrs:
+ client.update_subnet_pool(obj, **attrs)
diff --git a/openstackclient/object/client.py b/openstackclient/object/client.py
index 3af6f8a..865f18f 100644
--- a/openstackclient/object/client.py
+++ b/openstackclient/object/client.py
@@ -15,8 +15,9 @@
"""Object client"""
+from osc_lib import utils
+
from openstackclient.api import object_store_v1
-from openstackclient.common import utils
DEFAULT_API_VERSION = '1'
API_VERSION_OPTION = 'os_object_api_version'
@@ -31,8 +32,8 @@ def make_client(instance):
endpoint = instance.get_endpoint_for_service_type(
'object-store',
- region_name=instance._region_name,
- interface=instance._interface,
+ region_name=instance.region_name,
+ interface=instance.interface,
)
client = object_store_v1.APIv1(
diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py
index 543ce4f..801fe45 100644
--- a/openstackclient/object/v1/account.py
+++ b/openstackclient/object/v1/account.py
@@ -13,12 +13,11 @@
"""Account v1 action implementations"""
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
-
class SetAccount(command.Command):
"""Set account properties"""
diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py
index 80b8423..f00cc15 100644
--- a/openstackclient/object/v1/container.py
+++ b/openstackclient/object/v1/container.py
@@ -15,13 +15,11 @@
"""Container v1 action implementations"""
-
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
-
class CreateContainer(command.Lister):
"""Create new container"""
diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py
index f9a55e9..39dba3d 100644
--- a/openstackclient/object/v1/object.py
+++ b/openstackclient/object/v1/object.py
@@ -16,12 +16,11 @@
"""Object v1 action implementations"""
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
-
class CreateObject(command.Lister):
"""Upload object to container"""
diff --git a/openstackclient/shell.py b/openstackclient/shell.py
index 9179ad0..26147be 100644
--- a/openstackclient/shell.py
+++ b/openstackclient/shell.py
@@ -16,28 +16,18 @@
"""Command-line interface to the OpenStack APIs"""
-import argparse
-import getpass
-import logging
+import locale
import sys
-import traceback
-from cliff import app
-from cliff import command
-from cliff import complete
-from cliff import help
+from osc_lib.api import auth
+from osc_lib import shell
from oslo_utils import importutils
-from oslo_utils import strutils
+import six
import openstackclient
+from openstackclient.common import client_config as cloud_config
from openstackclient.common import clientmanager
from openstackclient.common import commandmanager
-from openstackclient.common import exceptions as exc
-from openstackclient.common import logs
-from openstackclient.common import timing
-from openstackclient.common import utils
-
-from os_client_config import config as cloud_config
osprofiler_profiler = importutils.try_import("osprofiler.profiler")
@@ -45,48 +35,9 @@ osprofiler_profiler = importutils.try_import("osprofiler.profiler")
DEFAULT_DOMAIN = 'default'
-def prompt_for_password(prompt=None):
- """Prompt user for a password
-
- Prompt for a password if stdin is a tty.
- """
-
- if not prompt:
- prompt = 'Password: '
- pw = None
- # If stdin is a tty, try prompting for the password
- if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
- # Check for Ctl-D
- try:
- pw = getpass.getpass(prompt)
- except EOFError:
- pass
- # No password because we did't have a tty or nothing was entered
- if not pw:
- raise exc.CommandError(
- "No password entered, or found via --os-password or OS_PASSWORD",
- )
- return pw
-
-
-class OpenStackShell(app.App):
-
- CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s'
-
- log = logging.getLogger(__name__)
- timing_data = []
+class OpenStackShell(shell.OpenStackShell):
def __init__(self):
- # Patch command.Command to add a default auth_required = True
- command.Command.auth_required = True
-
- # Some commands do not need authentication
- help.HelpCommand.auth_required = False
- complete.CompleteCommand.auth_required = False
-
- # Slight change to the meaning of --debug
- self.DEFAULT_DEBUG_VALUE = None
- self.DEFAULT_DEBUG_HELP = 'Set debug logging and traceback on errors.'
super(OpenStackShell, self).__init__(
description=__doc__.strip(),
@@ -96,276 +47,32 @@ class OpenStackShell(app.App):
self.api_version = {}
- # Until we have command line arguments parsed, dump any stack traces
- self.dump_stack_trace = True
-
# Assume TLS host certificate verification is enabled
self.verify = True
- self.client_manager = None
- self.command_options = None
-
- self.do_profile = False
-
- def configure_logging(self):
- """Configure logging for the app."""
- self.log_configurator = logs.LogConfigurator(self.options)
- self.dump_stack_trace = self.log_configurator.dump_trace
-
- def run(self, argv):
- ret_val = 1
- self.command_options = argv
- try:
- ret_val = super(OpenStackShell, self).run(argv)
- return ret_val
- except Exception as e:
- if not logging.getLogger('').handlers:
- logging.basicConfig()
- if self.dump_stack_trace:
- self.log.error(traceback.format_exc())
- else:
- self.log.error('Exception raised: ' + str(e))
-
- return ret_val
-
- finally:
- self.log.info("END return value: %s", ret_val)
-
- def init_profile(self):
- # NOTE(dtroyer): Remove this 'if' block when the --profile global
- # option is removed
- if osprofiler_profiler and self.options.old_profile:
- self.log.warning(
- 'The --profile option is deprecated, '
- 'please use --os-profile instead'
- )
- if not self.options.profile:
- self.options.profile = self.options.old_profile
-
- self.do_profile = osprofiler_profiler and self.options.profile
- if self.do_profile:
- osprofiler_profiler.init(self.options.profile)
-
- def close_profile(self):
- if self.do_profile:
- trace_id = osprofiler_profiler.get().get_base_id()
-
- # NOTE(dbelova): let's use warning log level to see these messages
- # printed. In fact we can define custom log level here with value
- # bigger than most big default one (CRITICAL) or something like
- # that (PROFILE = 60 for instance), but not sure we need it here.
- self.log.warning("Trace ID: %s" % trace_id)
- self.log.warning("Display trace with command:\n"
- "osprofiler trace show --html %s " % trace_id)
-
- def run_subcommand(self, argv):
- self.init_profile()
- try:
- ret_value = super(OpenStackShell, self).run_subcommand(argv)
- finally:
- self.close_profile()
- return ret_value
-
- def interact(self):
- self.init_profile()
- try:
- ret_value = super(OpenStackShell, self).interact()
- finally:
- self.close_profile()
- return ret_value
-
def build_option_parser(self, description, version):
parser = super(OpenStackShell, self).build_option_parser(
description,
version)
+ parser = clientmanager.build_plugin_option_parser(parser)
+ parser = auth.build_auth_plugins_option_parser(parser)
+ return parser
- # service token auth argument
- parser.add_argument(
- '--os-cloud',
- metavar='<cloud-config-name>',
- dest='cloud',
- default=utils.env('OS_CLOUD'),
- help='Cloud name in clouds.yaml (Env: OS_CLOUD)',
- )
- # Global arguments
- parser.add_argument(
- '--os-region-name',
- metavar='<auth-region-name>',
- dest='region_name',
- default=utils.env('OS_REGION_NAME'),
- help='Authentication region name (Env: OS_REGION_NAME)')
- parser.add_argument(
- '--os-cacert',
- metavar='<ca-bundle-file>',
- dest='cacert',
- default=utils.env('OS_CACERT'),
- help='CA certificate bundle file (Env: OS_CACERT)')
- parser.add_argument(
- '--os-cert',
- metavar='<certificate-file>',
- dest='cert',
- default=utils.env('OS_CERT'),
- help='Client certificate bundle file (Env: OS_CERT)')
- parser.add_argument(
- '--os-key',
- metavar='<key-file>',
- dest='key',
- default=utils.env('OS_KEY'),
- help='Client certificate key file (Env: OS_KEY)')
- verify_group = parser.add_mutually_exclusive_group()
- verify_group.add_argument(
- '--verify',
- action='store_true',
- default=None,
- help='Verify server certificate (default)',
- )
- verify_group.add_argument(
- '--insecure',
- action='store_true',
- default=None,
- help='Disable server certificate verification',
- )
- parser.add_argument(
- '--os-default-domain',
- metavar='<auth-domain>',
- dest='default_domain',
- default=utils.env(
- 'OS_DEFAULT_DOMAIN',
- default=DEFAULT_DOMAIN),
- help='Default domain ID, default=' +
- DEFAULT_DOMAIN +
- ' (Env: OS_DEFAULT_DOMAIN)')
- parser.add_argument(
- '--os-interface',
- metavar='<interface>',
- dest='interface',
- choices=['admin', 'public', 'internal'],
- default=utils.env('OS_INTERFACE'),
- help='Select an interface type.'
- ' Valid interface types: [admin, public, internal].'
- ' (Env: OS_INTERFACE)')
- parser.add_argument(
- '--timing',
- default=False,
- action='store_true',
- help="Print API call timing info",
- )
- parser.add_argument(
- '--os-beta-command',
- action='store_true',
- help="Enable beta commands which are subject to change",
- )
-
- # osprofiler HMAC key argument
- if osprofiler_profiler:
- parser.add_argument(
- '--os-profile',
- metavar='hmac-key',
- dest='profile',
- help='HMAC key for encrypting profiling context data',
- )
- # NOTE(dtroyer): This global option should have been named
- # --os-profile as --profile interferes with at
- # least one existing command option. Deprecate
- # --profile and remove after Apr 2017.
- parser.add_argument(
- '--profile',
- metavar='hmac-key',
- dest='old_profile',
- help=argparse.SUPPRESS,
- )
-
- return clientmanager.build_plugin_option_parser(parser)
-
- def initialize_app(self, argv):
- """Global app init bits:
-
- * set up API versions
- * validate authentication info
- * authenticate against Identity if requested
- """
-
- # Parent __init__ parses argv into self.options
- super(OpenStackShell, self).initialize_app(argv)
- self.log.info("START with options: %s",
- strutils.mask_password(self.command_options))
- self.log.debug("options: %s",
- strutils.mask_password(self.options))
+ def _final_defaults(self):
+ super(OpenStackShell, self)._final_defaults()
# Set the default plugin to token_endpoint if url and token are given
if (self.options.url and self.options.token):
# Use service token authentication
- auth_type = 'token_endpoint'
+ self._auth_type = 'token_endpoint'
else:
- auth_type = 'password'
-
- project_id = getattr(self.options, 'project_id', None)
- project_name = getattr(self.options, 'project_name', None)
- tenant_id = getattr(self.options, 'tenant_id', None)
- tenant_name = getattr(self.options, 'tenant_name', None)
-
- # handle some v2/v3 authentication inconsistencies by just acting like
- # both the project and tenant information are both present. This can
- # go away if we stop registering all the argparse options together.
- if project_id and not tenant_id:
- self.options.tenant_id = project_id
- if project_name and not tenant_name:
- self.options.tenant_name = project_name
- if tenant_id and not project_id:
- self.options.project_id = tenant_id
- if tenant_name and not project_name:
- self.options.project_name = tenant_name
-
- # Do configuration file handling
- # Ignore the default value of interface. Only if it is set later
- # will it be used.
- try:
- cc = cloud_config.OpenStackConfig(
- override_defaults={
- 'interface': None,
- 'auth_type': auth_type,
- },
- )
- except (IOError, OSError) as e:
- self.log.critical("Could not read clouds.yaml configuration file")
- self.print_help_if_requested()
- raise e
+ self._auth_type = 'password'
- # TODO(thowe): Change cliff so the default value for debug
- # can be set to None.
- if not self.options.debug:
- self.options.debug = None
- self.cloud = cc.get_one_cloud(
- cloud=self.options.cloud,
- argparse=self.options,
- )
-
- self.log_configurator.configure(self.cloud)
- self.dump_stack_trace = self.log_configurator.dump_trace
- self.log.debug("defaults: %s", cc.defaults)
- self.log.debug("cloud cfg: %s",
- strutils.mask_password(self.cloud.config))
-
- # Set up client TLS
- # NOTE(dtroyer): --insecure is the non-default condition that
- # overrides any verify setting in clouds.yaml
- # so check it first, then fall back to any verify
- # setting provided.
- self.verify = not self.cloud.config.get(
- 'insecure',
- not self.cloud.config.get('verify', True),
- )
-
- # NOTE(dtroyer): Per bug https://bugs.launchpad.net/bugs/1447784
- # --insecure now overrides any --os-cacert setting,
- # where before --insecure was ignored if --os-cacert
- # was set.
- if self.verify and self.cloud.cacert:
- self.verify = self.cloud.cacert
-
- # Save default domain
- self.default_domain = self.options.default_domain
+ def _load_plugins(self):
+ """Load plugins via stevedore
+ osc-lib has no opinion on what plugins should be loaded
+ """
# Loop through extensions to get API versions
for mod in clientmanager.PLUGIN_MODULES:
default_version = getattr(mod, 'DEFAULT_API_VERSION', None)
@@ -400,6 +107,11 @@ class OpenStackShell(app.App):
{'name': api, 'version': version_opt, 'group': cmd_group}
)
+ def _load_commands(self):
+ """Load commands via cliff/stevedore
+
+ osc-lib has no opinion on what commands should be loaded
+ """
# Commands that span multiple APIs
self.command_manager.add_command_group(
'openstack.common')
@@ -416,63 +128,55 @@ class OpenStackShell(app.App):
# }
self.command_manager.add_command_group(
'openstack.extension')
- # call InitializeXxx() here
- # set up additional clients to stuff in to client_manager??
- # Handle deferred help and exit
- self.print_help_if_requested()
+ def initialize_app(self, argv):
+ super(OpenStackShell, self).initialize_app(argv)
- self.client_manager = clientmanager.ClientManager(
- cli_options=self.cloud,
- verify=self.verify,
- api_version=self.api_version,
- pw_func=prompt_for_password,
- )
+ # Argument precedence is really broken in multiple places
+ # so we're just going to fix it here until o-c-c and osc-lib
+ # get sorted out.
+ # TODO(dtroyer): remove when os-client-config and osc-lib are fixed
- def prepare_to_run_command(self, cmd):
- """Set up auth and API versions"""
- self.log.info(
- 'command: %s -> %s.%s',
- getattr(cmd, 'cmd_name', '<none>'),
- cmd.__class__.__module__,
- cmd.__class__.__name__,
- )
- if cmd.auth_required:
- if hasattr(cmd, 'required_scope'):
- # let the command decide whether we need a scoped token
- self.client_manager.setup_auth(cmd.required_scope)
- # Trigger the Identity client to initialize
- self.client_manager.auth_ref
- return
-
- def clean_up(self, cmd, result, err):
- self.log.debug('clean_up %s: %s', cmd.__class__.__name__, err or '')
-
- # Process collected timing data
- if self.options.timing:
- # Get session data
- self.timing_data.extend(
- self.client_manager.session.get_timings(),
+ # First, throw away what has already been done with o-c-c and
+ # use our own.
+ try:
+ cc = cloud_config.OSC_Config(
+ override_defaults={
+ 'interface': None,
+ 'auth_type': self._auth_type,
+ },
+ pw_func=shell.prompt_for_password,
)
+ except (IOError, OSError) as e:
+ self.log.critical("Could not read clouds.yaml configuration file")
+ self.print_help_if_requested()
+ raise e
- # Use the Timing pseudo-command to generate the output
- tcmd = timing.Timing(self, self.options)
- tparser = tcmd.get_parser('Timing')
+ if not self.options.debug:
+ self.options.debug = None
+ self.cloud = cc.get_one_cloud(
+ cloud=self.options.cloud,
+ argparse=self.options,
+ )
- # If anything other than prettytable is specified, force csv
- format = 'table'
- # Check the formatter used in the actual command
- if hasattr(cmd, 'formatter') \
- and cmd.formatter != cmd._formatter_plugins['table'].obj:
- format = 'csv'
+ # Then, re-create the client_manager with the correct arguments
+ self.client_manager = clientmanager.ClientManager(
+ cli_options=self.cloud,
+ api_version=self.api_version,
+ )
- sys.stdout.write('\n')
- targs = tparser.parse_args(['-f', format])
- tcmd.run(targs)
+def main(argv=None):
+ if argv is None:
+ argv = sys.argv[1:]
+ if six.PY2:
+ # Emulate Py3, decode argv into Unicode based on locale so that
+ # commands always see arguments as text instead of binary data
+ encoding = locale.getpreferredencoding()
+ if encoding:
+ argv = map(lambda arg: arg.decode(encoding), argv)
-def main(argv=sys.argv[1:]):
return OpenStackShell().run(argv)
if __name__ == "__main__":
- sys.exit(main(sys.argv[1:]))
+ sys.exit(main())
diff --git a/openstackclient/tests/api/fakes.py b/openstackclient/tests/api/fakes.py
index e285a61..8d1d88f 100644
--- a/openstackclient/tests/api/fakes.py
+++ b/openstackclient/tests/api/fakes.py
@@ -13,9 +13,9 @@
"""API Test Fakes"""
+from keystoneauth1 import session
from requests_mock.contrib import fixture
-from keystoneauth1 import session
from openstackclient.tests import utils
diff --git a/openstackclient/tests/api/test_api.py b/openstackclient/tests/api/test_api.py
index 8119796..b444d9f 100644
--- a/openstackclient/tests/api/test_api.py
+++ b/openstackclient/tests/api/test_api.py
@@ -13,8 +13,9 @@
"""Base API Library Tests"""
+from osc_lib import exceptions
+
from openstackclient.api import api
-from openstackclient.common import exceptions
from openstackclient.tests.api import fakes as api_fakes
diff --git a/openstackclient/tests/api/test_image_v1.py b/openstackclient/tests/api/test_image_v1.py
index f347975..f8ad669 100644
--- a/openstackclient/tests/api/test_image_v1.py
+++ b/openstackclient/tests/api/test_image_v1.py
@@ -13,9 +13,9 @@
"""Image v1 API Library Tests"""
+from keystoneauth1 import session
from requests_mock.contrib import fixture
-from keystoneauth1 import session
from openstackclient.api import image_v1
from openstackclient.tests import utils
diff --git a/openstackclient/tests/api/test_image_v2.py b/openstackclient/tests/api/test_image_v2.py
index 7706399..28b0d58 100644
--- a/openstackclient/tests/api/test_image_v2.py
+++ b/openstackclient/tests/api/test_image_v2.py
@@ -13,9 +13,9 @@
"""Image v2 API Library Tests"""
+from keystoneauth1 import session
from requests_mock.contrib import fixture
-from keystoneauth1 import session
from openstackclient.api import image_v2
from openstackclient.tests import utils
diff --git a/openstackclient/tests/api/test_object_store_v1.py b/openstackclient/tests/api/test_object_store_v1.py
index 8cc3a92..e6ac203 100644
--- a/openstackclient/tests/api/test_object_store_v1.py
+++ b/openstackclient/tests/api/test_object_store_v1.py
@@ -15,9 +15,9 @@
import mock
+from keystoneauth1 import session
from requests_mock.contrib import fixture
-from keystoneauth1 import session
from openstackclient.api import object_store_v1 as object_store
from openstackclient.tests import utils
diff --git a/openstackclient/tests/common/test_availability_zone.py b/openstackclient/tests/common/test_availability_zone.py
index feecaf5..014ab8b 100644
--- a/openstackclient/tests/common/test_availability_zone.py
+++ b/openstackclient/tests/common/test_availability_zone.py
@@ -12,6 +12,7 @@
#
import mock
+
import six
from openstackclient.common import availability_zone
diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py
index fa6c3fc..625e175 100644
--- a/openstackclient/tests/common/test_clientmanager.py
+++ b/openstackclient/tests/common/test_clientmanager.py
@@ -13,112 +13,34 @@
# under the License.
#
-import json as jsonutils
-import mock
-from requests_mock.contrib import fixture
+import copy
-from keystoneclient.auth.identity import v2 as auth_v2
-from keystoneclient import service_catalog
+from keystoneauth1 import token_endpoint
+from osc_lib.tests import utils as osc_lib_test_utils
-from openstackclient.api import auth
-from openstackclient.api import auth_plugin
from openstackclient.common import clientmanager
-from openstackclient.common import exceptions as exc
from openstackclient.tests import fakes
-from openstackclient.tests import utils
-API_VERSION = {"identity": "2.0"}
+class TestClientManager(osc_lib_test_utils.TestClientManager):
-AUTH_REF = {'version': 'v2.0'}
-AUTH_REF.update(fakes.TEST_RESPONSE_DICT['access'])
-SERVICE_CATALOG = service_catalog.ServiceCatalogV2(AUTH_REF)
-
-
-# This is deferred in api.auth but we need it here...
-auth.get_options_list()
-
-
-class Container(object):
- attr = clientmanager.ClientCache(lambda x: object())
- buggy_attr = clientmanager.ClientCache(lambda x: x.foo)
-
- def __init__(self):
- pass
-
-
-class FakeOptions(object):
-
- def __init__(self, **kwargs):
- for option in auth.OPTIONS_LIST:
- setattr(self, option.replace('-', '_'), None)
- self.auth_type = None
- self.identity_api_version = '2.0'
- self.timing = None
- self.region_name = None
- self.interface = None
- self.url = None
- self.auth = {}
- self.cert = None
- self.key = None
- self.default_domain = 'default'
- self.__dict__.update(kwargs)
-
-
-class TestClientCache(utils.TestCase):
-
- def test_singleton(self):
- # NOTE(dtroyer): Verify that the ClientCache descriptor only invokes
- # the factory one time and always returns the same value after that.
- c = Container()
- self.assertEqual(c.attr, c.attr)
-
- def test_attribute_error_propagates(self):
- c = Container()
- err = self.assertRaises(exc.PluginAttributeError,
- getattr, c, 'buggy_attr')
- self.assertNotIsInstance(err, AttributeError)
- self.assertEqual("'Container' object has no attribute 'foo'", str(err))
-
-
-class TestClientManager(utils.TestCase):
-
- def setUp(self):
- super(TestClientManager, self).setUp()
- self.mock = mock.Mock()
- self.requests = self.useFixture(fixture.Fixture())
- # fake v2password token retrieval
- self.stub_auth(json=fakes.TEST_RESPONSE_DICT)
- # fake token and token_endpoint retrieval
- self.stub_auth(json=fakes.TEST_RESPONSE_DICT,
- url='/'.join([fakes.AUTH_URL, 'v2.0/tokens']))
- # fake v3password token retrieval
- self.stub_auth(json=fakes.TEST_RESPONSE_DICT_V3,
- url='/'.join([fakes.AUTH_URL, 'auth/tokens']))
- # fake password version endpoint discovery
- self.stub_auth(json=fakes.TEST_VERSIONS,
- url=fakes.AUTH_URL,
- verb='GET')
+ def _clientmanager_class(self):
+ """Allow subclasses to override the ClientManager class"""
+ return clientmanager.ClientManager
def test_client_manager_token_endpoint(self):
-
- client_manager = clientmanager.ClientManager(
- cli_options=FakeOptions(
- auth_type='token_endpoint',
- auth=dict(
- token=fakes.AUTH_TOKEN,
- url=fakes.AUTH_URL,
- ),
- ),
- api_version=API_VERSION,
- verify=True
+ token_auth = {
+ 'url': fakes.AUTH_URL,
+ 'token': fakes.AUTH_TOKEN,
+ }
+ client_manager = self._make_clientmanager(
+ auth_args=token_auth,
+ auth_plugin_name='token_endpoint',
)
- client_manager.setup_auth()
- client_manager.auth_ref
self.assertEqual(
fakes.AUTH_URL,
- client_manager._url,
+ client_manager._cli_options.config['auth']['url'],
)
self.assertEqual(
fakes.AUTH_TOKEN,
@@ -126,254 +48,22 @@ class TestClientManager(utils.TestCase):
)
self.assertIsInstance(
client_manager.auth,
- auth_plugin.TokenEndpoint,
- )
- self.assertFalse(client_manager._insecure)
- self.assertTrue(client_manager._verify)
- self.assertTrue(client_manager.is_network_endpoint_enabled())
-
- def test_client_manager_token(self):
-
- client_manager = clientmanager.ClientManager(
- cli_options=FakeOptions(
- auth=dict(
- token=fakes.AUTH_TOKEN,
- auth_url=fakes.AUTH_URL,
- ),
- auth_type='v2token',
- interface=fakes.INTERFACE,
- region_name=fakes.REGION_NAME,
- ),
- api_version=API_VERSION,
- verify=True
- )
- client_manager.setup_auth()
- client_manager.auth_ref
-
- self.assertEqual(
- fakes.AUTH_URL,
- client_manager._auth_url,
- )
- self.assertIsInstance(
- client_manager.auth,
- auth_v2.Token,
- )
- self.assertEqual(
- fakes.INTERFACE,
- client_manager._interface,
- )
- self.assertEqual(
- fakes.REGION_NAME,
- client_manager._region_name,
- )
- self.assertFalse(client_manager._insecure)
- self.assertTrue(client_manager._verify)
- self.assertTrue(client_manager.is_network_endpoint_enabled())
-
- def test_client_manager_password(self):
-
- client_manager = clientmanager.ClientManager(
- cli_options=FakeOptions(
- auth=dict(
- auth_url=fakes.AUTH_URL,
- username=fakes.USERNAME,
- password=fakes.PASSWORD,
- project_name=fakes.PROJECT_NAME,
- ),
- ),
- api_version=API_VERSION,
- verify=False,
- )
- client_manager.setup_auth()
- client_manager.auth_ref
-
- self.assertEqual(
- fakes.AUTH_URL,
- client_manager._auth_url,
- )
- self.assertEqual(
- fakes.USERNAME,
- client_manager._username,
- )
- self.assertEqual(
- fakes.PASSWORD,
- client_manager._password,
- )
- self.assertIsInstance(
- client_manager.auth,
- auth_v2.Password,
- )
- self.assertTrue(client_manager._insecure)
- self.assertFalse(client_manager._verify)
-
- # These need to stick around until the old-style clients are gone
- self.assertEqual(
- AUTH_REF,
- client_manager.auth_ref,
- )
- self.assertEqual(
- dir(SERVICE_CATALOG),
- dir(client_manager.auth_ref.service_catalog),
+ token_endpoint.Token,
)
self.assertTrue(client_manager.is_network_endpoint_enabled())
def test_client_manager_network_endpoint_disabled(self):
-
- client_manager = clientmanager.ClientManager(
- cli_options=FakeOptions(
- auth=dict(
- auth_url=fakes.AUTH_URL,
- username=fakes.USERNAME,
- password=fakes.PASSWORD,
- project_name=fakes.PROJECT_NAME,
- ),
- auth_type='v3password',
- ),
- api_version={"identity": "3"},
- verify=False,
- )
- client_manager.setup_auth()
- client_manager.auth_ref
-
- # v3 fake doesn't have network endpoint.
+ auth_args = copy.deepcopy(self.default_password_auth)
+ auth_args.update({
+ 'user_domain_name': 'default',
+ 'project_domain_name': 'default',
+ })
+ # v3 fake doesn't have network endpoint
+ client_manager = self._make_clientmanager(
+ auth_args=auth_args,
+ identity_api_version='3',
+ auth_plugin_name='v3password',
+ )
+
+ self.assertFalse(client_manager.is_service_available('network'))
self.assertFalse(client_manager.is_network_endpoint_enabled())
-
- def stub_auth(self, json=None, url=None, verb=None, **kwargs):
- subject_token = fakes.AUTH_TOKEN
- base_url = fakes.AUTH_URL
- if json:
- text = jsonutils.dumps(json)
- headers = {'X-Subject-Token': subject_token,
- 'Content-Type': 'application/json'}
- if not url:
- url = '/'.join([base_url, 'tokens'])
- url = url.replace("/?", "?")
- if not verb:
- verb = 'POST'
- self.requests.register_uri(verb,
- url,
- headers=headers,
- text=text)
-
- def test_client_manager_password_verify_ca(self):
-
- client_manager = clientmanager.ClientManager(
- cli_options=FakeOptions(
- auth=dict(
- auth_url=fakes.AUTH_URL,
- username=fakes.USERNAME,
- password=fakes.PASSWORD,
- project_name=fakes.PROJECT_NAME,
- ),
- auth_type='v2password',
- ),
- api_version=API_VERSION,
- verify='cafile',
- )
- client_manager.setup_auth()
- client_manager.auth_ref
-
- self.assertFalse(client_manager._insecure)
- self.assertTrue(client_manager._verify)
- self.assertEqual('cafile', client_manager._cacert)
- self.assertTrue(client_manager.is_network_endpoint_enabled())
-
- def test_client_manager_password_no_cert(self):
- client_manager = clientmanager.ClientManager(
- cli_options=FakeOptions())
- self.assertIsNone(client_manager._cert)
-
- def test_client_manager_password_client_cert(self):
- client_manager = clientmanager.ClientManager(
- cli_options=FakeOptions(cert='cert'))
- self.assertEqual('cert', client_manager._cert)
-
- def test_client_manager_password_client_cert_and_key(self):
- client_manager = clientmanager.ClientManager(
- cli_options=FakeOptions(cert='cert', key='key'))
- self.assertEqual(('cert', 'key'), client_manager._cert)
-
- def _select_auth_plugin(self, auth_params, api_version, auth_plugin_name):
- auth_params['auth_type'] = auth_plugin_name
- auth_params['identity_api_version'] = api_version
- client_manager = clientmanager.ClientManager(
- cli_options=FakeOptions(**auth_params),
- api_version=API_VERSION,
- verify=True
- )
- client_manager.setup_auth()
- client_manager.auth_ref
-
- self.assertEqual(
- auth_plugin_name,
- client_manager.auth_plugin_name,
- )
-
- def test_client_manager_select_auth_plugin(self):
- # test token auth
- params = dict(
- auth=dict(
- auth_url=fakes.AUTH_URL,
- token=fakes.AUTH_TOKEN,
- ),
- )
- self._select_auth_plugin(params, '2.0', 'v2token')
- self._select_auth_plugin(params, '3', 'v3token')
- self._select_auth_plugin(params, 'XXX', 'token')
- # test token/endpoint auth
- params = dict(
- auth_plugin='token_endpoint',
- auth=dict(
- url='test',
- token=fakes.AUTH_TOKEN,
- ),
- )
- self._select_auth_plugin(params, 'XXX', 'token_endpoint')
- # test password auth
- params = dict(
- auth=dict(
- auth_url=fakes.AUTH_URL,
- username=fakes.USERNAME,
- password=fakes.PASSWORD,
- project_name=fakes.PROJECT_NAME,
- ),
- )
- self._select_auth_plugin(params, '2.0', 'v2password')
- self._select_auth_plugin(params, '3', 'v3password')
- self._select_auth_plugin(params, 'XXX', 'password')
-
- def test_client_manager_select_auth_plugin_failure(self):
- client_manager = clientmanager.ClientManager(
- cli_options=FakeOptions(os_auth_plugin=''),
- api_version=API_VERSION,
- verify=True,
- )
- self.assertRaises(
- exc.CommandError,
- client_manager.setup_auth,
- )
-
- @mock.patch('openstackclient.api.auth.check_valid_auth_options')
- def test_client_manager_auth_setup_once(self, check_auth_options_func):
- client_manager = clientmanager.ClientManager(
- cli_options=FakeOptions(
- auth=dict(
- auth_url=fakes.AUTH_URL,
- username=fakes.USERNAME,
- password=fakes.PASSWORD,
- project_name=fakes.PROJECT_NAME,
- ),
- ),
- api_version=API_VERSION,
- verify=False,
- )
- self.assertFalse(client_manager._auth_setup_completed)
- client_manager.setup_auth()
- self.assertTrue(check_auth_options_func.called)
- self.assertTrue(client_manager._auth_setup_completed)
-
- # now make sure we don't do auth setup the second time around
- # by checking whether check_valid_auth_options() gets called again
- check_auth_options_func.reset_mock()
- client_manager.auth_ref
- check_auth_options_func.assert_not_called()
diff --git a/openstackclient/tests/common/test_command.py b/openstackclient/tests/common/test_command.py
index 722a4c0..658bc89 100644
--- a/openstackclient/tests/common/test_command.py
+++ b/openstackclient/tests/common/test_command.py
@@ -14,8 +14,9 @@
import mock
+from osc_lib import exceptions
+
from openstackclient.common import command
-from openstackclient.common import exceptions
from openstackclient.tests import fakes as test_fakes
from openstackclient.tests import utils as test_utils
diff --git a/openstackclient/tests/common/test_configuration.py b/openstackclient/tests/common/test_configuration.py
index e81550e..915e5bd 100644
--- a/openstackclient/tests/common/test_configuration.py
+++ b/openstackclient/tests/common/test_configuration.py
@@ -11,6 +11,8 @@
# under the License.
#
+import mock
+
from openstackclient.common import configuration
from openstackclient.tests import fakes
from openstackclient.tests import utils
@@ -33,7 +35,12 @@ class TestConfiguration(utils.TestCommand):
fakes.REGION_NAME,
)
- def test_show(self):
+ opts = [mock.Mock(secret=True, dest="password"),
+ mock.Mock(secret=True, dest="token")]
+
+ @mock.patch("keystoneauth1.loading.base.get_plugin_options",
+ return_value=opts)
+ def test_show(self, m_get_plugin_opts):
arglist = []
verifylist = [('mask', True)]
cmd = configuration.ShowConfiguration(self.app, None)
@@ -44,7 +51,9 @@ class TestConfiguration(utils.TestCommand):
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, data)
- def test_show_unmask(self):
+ @mock.patch("keystoneauth1.loading.base.get_plugin_options",
+ return_value=opts)
+ def test_show_unmask(self, m_get_plugin_opts):
arglist = ['--unmask']
verifylist = [('mask', False)]
cmd = configuration.ShowConfiguration(self.app, None)
@@ -62,7 +71,9 @@ class TestConfiguration(utils.TestCommand):
)
self.assertEqual(datalist, data)
- def test_show_mask(self):
+ @mock.patch("keystoneauth1.loading.base.get_plugin_options",
+ return_value=opts)
+ def test_show_mask(self, m_get_plugin_opts):
arglist = ['--mask']
verifylist = [('mask', True)]
cmd = configuration.ShowConfiguration(self.app, None)
diff --git a/openstackclient/tests/common/test_extension.py b/openstackclient/tests/common/test_extension.py
index 0736a3e..10023aa 100644
--- a/openstackclient/tests/common/test_extension.py
+++ b/openstackclient/tests/common/test_extension.py
@@ -11,16 +11,14 @@
# under the License.
#
-import copy
import mock
from openstackclient.common import extension
-from openstackclient.tests import fakes
-from openstackclient.tests import utils
-
from openstackclient.tests.compute.v2 import fakes as compute_fakes
+from openstackclient.tests import fakes
from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
from openstackclient.tests.network.v2 import fakes as network_fakes
+from openstackclient.tests import utils
from openstackclient.tests.volume.v2 import fakes as volume_fakes
@@ -29,26 +27,38 @@ class TestExtension(utils.TestCommand):
def setUp(self):
super(TestExtension, self).setUp()
- self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client(
+ identity_client = identity_fakes.FakeIdentityv2Client(
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)
- self.identity_extensions_mock = (
- self.app.client_manager.identity.extensions)
+ self.app.client_manager.identity = identity_client
+ self.identity_extensions_mock = identity_client.extensions
self.identity_extensions_mock.reset_mock()
- self.app.client_manager.compute = compute_fakes.FakeComputev2Client(
+ compute_client = compute_fakes.FakeComputev2Client(
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)
+ self.app.client_manager.compute = compute_client
+ compute_client.list_extensions = mock.Mock()
+ self.compute_extensions_mock = compute_client.list_extensions
+ self.compute_extensions_mock.reset_mock()
- self.app.client_manager.volume = volume_fakes.FakeVolumeClient(
+ volume_client = volume_fakes.FakeVolumeClient(
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)
+ self.app.client_manager.volume = volume_client
+ volume_client.list_extensions = mock.Mock()
+ self.volume_extensions_mock = volume_client.list_extensions
+ self.volume_extensions_mock.reset_mock()
- network_client = network_fakes.FakeNetworkV2Client()
+ network_client = network_fakes.FakeNetworkV2Client(
+ endpoint=fakes.AUTH_URL,
+ token=fakes.AUTH_TOKEN,
+ )
self.app.client_manager.network = network_client
+ network_client.extensions = mock.Mock()
self.network_extensions_mock = network_client.extensions
self.network_extensions_mock.reset_mock()
@@ -59,38 +69,21 @@ class TestExtensionList(TestExtension):
long_columns = ('Name', 'Namespace', 'Description', 'Alias', 'Updated',
'Links')
+ volume_extension = volume_fakes.FakeExtension.create_one_extension()
+ identity_extension = identity_fakes.FakeExtension.create_one_extension()
+ compute_extension = compute_fakes.FakeExtension.create_one_extension()
+ network_extension = network_fakes.FakeExtension.create_one_extension()
+
def setUp(self):
super(TestExtensionList, self).setUp()
self.identity_extensions_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.EXTENSION),
- loaded=True,
- ),
- ]
-
- self.app.client_manager.compute.list_extensions = mock.Mock()
- self.compute_extensions_mock = (
- self.app.client_manager.compute.list_extensions)
+ self.identity_extension]
self.compute_extensions_mock.show_all.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(compute_fakes.EXTENSION),
- loaded=True,
- ),
- ]
-
- self.app.client_manager.volume.list_extensions = mock.Mock()
- self.volume_extensions_mock = (
- self.app.client_manager.volume.list_extensions)
+ self.compute_extension]
self.volume_extensions_mock.show_all.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(volume_fakes.EXTENSION),
- loaded=True,
- ),
- ]
+ self.volume_extension]
+ self.network_extensions_mock.return_value = [self.network_extension]
# Get the command object to test
self.cmd = extension.ListExtension(self.app, None)
@@ -115,24 +108,24 @@ class TestExtensionList(TestExtension):
verifylist = []
datalist = (
(
- identity_fakes.extension_name,
- identity_fakes.extension_alias,
- identity_fakes.extension_description,
+ self.identity_extension.name,
+ self.identity_extension.alias,
+ self.identity_extension.description,
),
(
- compute_fakes.extension_name,
- compute_fakes.extension_alias,
- compute_fakes.extension_description,
+ self.compute_extension.name,
+ self.compute_extension.alias,
+ self.compute_extension.description,
),
(
- volume_fakes.extension_name,
- volume_fakes.extension_alias,
- volume_fakes.extension_description,
+ self.volume_extension.name,
+ self.volume_extension.alias,
+ self.volume_extension.description,
),
(
- network_fakes.extension_name,
- network_fakes.extension_alias,
- network_fakes.extension_description,
+ self.network_extension.name,
+ self.network_extension.alias,
+ self.network_extension.description,
),
)
self._test_extension_list_helper(arglist, verifylist, datalist)
@@ -150,36 +143,36 @@ class TestExtensionList(TestExtension):
]
datalist = (
(
- identity_fakes.extension_name,
- identity_fakes.extension_namespace,
- identity_fakes.extension_description,
- identity_fakes.extension_alias,
- identity_fakes.extension_updated,
- identity_fakes.extension_links,
+ self.identity_extension.name,
+ self.identity_extension.namespace,
+ self.identity_extension.description,
+ self.identity_extension.alias,
+ self.identity_extension.updated,
+ self.identity_extension.links,
),
(
- compute_fakes.extension_name,
- compute_fakes.extension_namespace,
- compute_fakes.extension_description,
- compute_fakes.extension_alias,
- compute_fakes.extension_updated,
- compute_fakes.extension_links,
+ self.compute_extension.name,
+ self.compute_extension.namespace,
+ self.compute_extension.description,
+ self.compute_extension.alias,
+ self.compute_extension.updated,
+ self.compute_extension.links,
),
(
- volume_fakes.extension_name,
- volume_fakes.extension_namespace,
- volume_fakes.extension_description,
- volume_fakes.extension_alias,
- volume_fakes.extension_updated,
- volume_fakes.extension_links,
+ self.volume_extension.name,
+ self.volume_extension.namespace,
+ self.volume_extension.description,
+ self.volume_extension.alias,
+ self.volume_extension.updated,
+ self.volume_extension.links,
),
(
- network_fakes.extension_name,
- network_fakes.extension_namespace,
- network_fakes.extension_description,
- network_fakes.extension_alias,
- network_fakes.extension_updated,
- network_fakes.extension_links,
+ self.network_extension.name,
+ self.network_extension.namespace,
+ self.network_extension.description,
+ self.network_extension.alias,
+ self.network_extension.updated,
+ self.network_extension.links,
),
)
self._test_extension_list_helper(arglist, verifylist, datalist, True)
@@ -196,9 +189,9 @@ class TestExtensionList(TestExtension):
('identity', True),
]
datalist = ((
- identity_fakes.extension_name,
- identity_fakes.extension_alias,
- identity_fakes.extension_description,
+ self.identity_extension.name,
+ self.identity_extension.alias,
+ self.identity_extension.description,
), )
self._test_extension_list_helper(arglist, verifylist, datalist)
self.identity_extensions_mock.list.assert_called_with()
@@ -212,9 +205,9 @@ class TestExtensionList(TestExtension):
]
datalist = (
(
- network_fakes.extension_name,
- network_fakes.extension_alias,
- network_fakes.extension_description,
+ self.network_extension.name,
+ self.network_extension.alias,
+ self.network_extension.description,
),
)
self._test_extension_list_helper(arglist, verifylist, datalist)
@@ -228,9 +221,9 @@ class TestExtensionList(TestExtension):
('compute', True),
]
datalist = ((
- compute_fakes.extension_name,
- compute_fakes.extension_alias,
- compute_fakes.extension_description,
+ self.compute_extension.name,
+ self.compute_extension.alias,
+ self.compute_extension.description,
), )
self._test_extension_list_helper(arglist, verifylist, datalist)
self.compute_extensions_mock.show_all.assert_called_with()
@@ -243,9 +236,9 @@ class TestExtensionList(TestExtension):
('volume', True),
]
datalist = ((
- volume_fakes.extension_name,
- volume_fakes.extension_alias,
- volume_fakes.extension_description,
+ self.volume_extension.name,
+ self.volume_extension.alias,
+ self.volume_extension.description,
), )
self._test_extension_list_helper(arglist, verifylist, datalist)
self.volume_extensions_mock.show_all.assert_called_with()
diff --git a/openstackclient/tests/common/test_logs.py b/openstackclient/tests/common/test_logs.py
index 0386cdf..5091510 100644
--- a/openstackclient/tests/common/test_logs.py
+++ b/openstackclient/tests/common/test_logs.py
@@ -11,6 +11,9 @@
# under the License.
#
+# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release
+# or Jun 2017.
+
import logging
import mock
@@ -121,7 +124,7 @@ class TestLogConfigurator(utils.TestCase):
@mock.patch('logging.StreamHandler')
@mock.patch('logging.getLogger')
- @mock.patch('openstackclient.common.logs.set_warning_filter')
+ @mock.patch('osc_lib.logs.set_warning_filter')
def test_init(self, warning_filter, getLogger, handle):
getLogger.side_effect = self.loggers
console_logger = mock.Mock()
@@ -142,7 +145,7 @@ class TestLogConfigurator(utils.TestCase):
self.assertFalse(configurator.dump_trace)
@mock.patch('logging.getLogger')
- @mock.patch('openstackclient.common.logs.set_warning_filter')
+ @mock.patch('osc_lib.logs.set_warning_filter')
def test_init_no_debug(self, warning_filter, getLogger):
getLogger.side_effect = self.loggers
self.options.debug = True
@@ -155,8 +158,8 @@ class TestLogConfigurator(utils.TestCase):
@mock.patch('logging.FileHandler')
@mock.patch('logging.getLogger')
- @mock.patch('openstackclient.common.logs.set_warning_filter')
- @mock.patch('openstackclient.common.logs._FileFormatter')
+ @mock.patch('osc_lib.logs.set_warning_filter')
+ @mock.patch('osc_lib.logs._FileFormatter')
def test_init_log_file(self, formatter, warning_filter, getLogger, handle):
getLogger.side_effect = self.loggers
self.options.log_file = '/tmp/log_file'
@@ -176,8 +179,8 @@ class TestLogConfigurator(utils.TestCase):
@mock.patch('logging.FileHandler')
@mock.patch('logging.getLogger')
- @mock.patch('openstackclient.common.logs.set_warning_filter')
- @mock.patch('openstackclient.common.logs._FileFormatter')
+ @mock.patch('osc_lib.logs.set_warning_filter')
+ @mock.patch('osc_lib.logs._FileFormatter')
def test_configure(self, formatter, warning_filter, getLogger, handle):
getLogger.side_effect = self.loggers
configurator = logs.LogConfigurator(self.options)
diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py
index 5c5ca3d..3038701 100644
--- a/openstackclient/tests/common/test_parseractions.py
+++ b/openstackclient/tests/common/test_parseractions.py
@@ -13,6 +13,9 @@
# under the License.
#
+# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release
+# or Jun 2017.
+
import argparse
from openstackclient.common import parseractions
@@ -46,19 +49,16 @@ class TestKeyValueAction(utils.TestCase):
actual = getattr(results, 'property', {})
# All should pass through unmolested
expect = {'red': '', 'green': '100%', 'blue': '50%', 'format': '#rgb'}
- self.assertDictEqual(expect, actual)
+ self.assertEqual(expect, actual)
def test_error_values(self):
- results = self.parser.parse_args([
- '--property', 'red',
- '--property', 'green=100%',
- '--property', 'blue',
- ])
-
- actual = getattr(results, 'property', {})
- # There should be no red or blue
- expect = {'green': '100%', 'format': '#rgb'}
- self.assertDictEqual(expect, actual)
+ self.assertRaises(
+ argparse.ArgumentTypeError,
+ self.parser.parse_args,
+ [
+ '--property', 'red',
+ ]
+ )
class TestMultiKeyValueAction(utils.TestCase):
diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py
index c9ec559..16fa35f 100644
--- a/openstackclient/tests/common/test_quota.py
+++ b/openstackclient/tests/common/test_quota.py
@@ -11,7 +11,6 @@
# under the License.
import copy
-
import mock
from openstackclient.common import quota
@@ -116,6 +115,8 @@ class TestQuotaSet(TestQuota):
'--properties', str(compute_fakes.property_num),
'--secgroup-rules', str(compute_fakes.secgroup_rule_num),
'--secgroups', str(compute_fakes.secgroup_num),
+ '--server-groups', str(compute_fakes.servgroup_num),
+ '--server-group-members', str(compute_fakes.servgroup_members_num),
identity_fakes.project_name,
]
verifylist = [
@@ -132,6 +133,8 @@ class TestQuotaSet(TestQuota):
('metadata_items', compute_fakes.property_num),
('security_group_rules', compute_fakes.secgroup_rule_num),
('security_groups', compute_fakes.secgroup_num),
+ ('server_groups', compute_fakes.servgroup_num),
+ ('server_group_members', compute_fakes.servgroup_members_num),
('project', identity_fakes.project_name),
]
@@ -154,6 +157,8 @@ class TestQuotaSet(TestQuota):
'metadata_items': compute_fakes.property_num,
'security_group_rules': compute_fakes.secgroup_rule_num,
'security_groups': compute_fakes.secgroup_num,
+ 'server_groups': compute_fakes.servgroup_num,
+ 'server_group_members': compute_fakes.servgroup_members_num,
}
self.quotas_mock.update.assert_called_with(
diff --git a/openstackclient/tests/common/test_timing.py b/openstackclient/tests/common/test_timing.py
deleted file mode 100644
index e33bb7a..0000000
--- a/openstackclient/tests/common/test_timing.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-#
-
-"""Test Timing pseudo-command"""
-
-import datetime
-
-from openstackclient.common import timing
-from openstackclient.tests import fakes
-from openstackclient.tests import utils
-
-
-timing_url = 'GET http://localhost:5000'
-timing_elapsed = 0.872809
-
-
-class FakeGenericClient(object):
-
- def __init__(self, **kwargs):
- self.auth_token = kwargs['token']
- self.management_url = kwargs['endpoint']
-
-
-class TestTiming(utils.TestCommand):
-
- columns = (
- 'URL',
- 'Seconds',
- )
-
- def setUp(self):
- super(TestTiming, self).setUp()
-
- self.app.timing_data = []
-
- self.app.client_manager.compute = FakeGenericClient(
- endpoint=fakes.AUTH_URL,
- token=fakes.AUTH_TOKEN,
- )
-
- self.app.client_manager.volume = FakeGenericClient(
- endpoint=fakes.AUTH_URL,
- token=fakes.AUTH_TOKEN,
- )
-
- # Get the command object to test
- self.cmd = timing.Timing(self.app, None)
-
- def test_timing_list_no_data(self):
- arglist = []
- verifylist = []
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- # In base command class Lister in cliff, abstract method take_action()
- # returns a tuple containing the column names and an iterable
- # containing the data to be listed.
- columns, data = self.cmd.take_action(parsed_args)
-
- self.assertEqual(self.columns, columns)
- datalist = [
- ('Total', 0.0,)
- ]
- self.assertEqual(datalist, data)
-
- def test_timing_list(self):
- self.app.timing_data = [(
- timing_url,
- datetime.timedelta(microseconds=timing_elapsed * 1000000),
- )]
-
- arglist = []
- verifylist = []
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- # In base command class Lister in cliff, abstract method take_action()
- # returns a tuple containing the column names and an iterable
- # containing the data to be listed.
- columns, data = self.cmd.take_action(parsed_args)
- self.assertEqual(self.columns, columns)
- datalist = [
- (timing_url, timing_elapsed),
- ('Total', timing_elapsed),
- ]
- self.assertEqual(datalist, data)
diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py
deleted file mode 100644
index 2248d04..0000000
--- a/openstackclient/tests/common/test_utils.py
+++ /dev/null
@@ -1,400 +0,0 @@
-# Copyright 2012-2013 OpenStack, LLC.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-#
-
-import time
-import uuid
-
-import mock
-
-from openstackclient.common import exceptions
-from openstackclient.common import utils
-from openstackclient.tests import fakes
-from openstackclient.tests import utils as test_utils
-
-PASSWORD = "Pa$$w0rd"
-WASSPORD = "Wa$$p0rd"
-DROWSSAP = "dr0w$$aP"
-
-
-class FakeOddballResource(fakes.FakeResource):
-
- def get(self, attr):
- """get() is needed for utils.find_resource()"""
- if attr == 'id':
- return self.id
- elif attr == 'name':
- return self.name
- else:
- return None
-
-
-class TestUtils(test_utils.TestCase):
-
- def test_get_password_good(self):
- with mock.patch("getpass.getpass", return_value=PASSWORD):
- mock_stdin = mock.Mock()
- mock_stdin.isatty = mock.Mock()
- mock_stdin.isatty.return_value = True
- self.assertEqual(PASSWORD, utils.get_password(mock_stdin))
-
- def test_get_password_bad_once(self):
- answers = [PASSWORD, WASSPORD, DROWSSAP, DROWSSAP]
- with mock.patch("getpass.getpass", side_effect=answers):
- mock_stdin = mock.Mock()
- mock_stdin.isatty = mock.Mock()
- mock_stdin.isatty.return_value = True
- self.assertEqual(DROWSSAP, utils.get_password(mock_stdin))
-
- def test_get_password_no_tty(self):
- mock_stdin = mock.Mock()
- mock_stdin.isatty = mock.Mock()
- mock_stdin.isatty.return_value = False
- self.assertRaises(exceptions.CommandError,
- utils.get_password,
- mock_stdin)
-
- def test_get_password_cntrl_d(self):
- with mock.patch("getpass.getpass", side_effect=EOFError()):
- mock_stdin = mock.Mock()
- mock_stdin.isatty = mock.Mock()
- mock_stdin.isatty.return_value = True
- self.assertRaises(exceptions.CommandError,
- utils.get_password,
- mock_stdin)
-
- def get_test_items(self):
- item1 = {'a': 1, 'b': 2}
- item2 = {'a': 1, 'b': 3}
- item3 = {'a': 2, 'b': 2}
- item4 = {'a': 2, 'b': 1}
- return [item1, item2, item3, item4]
-
- def test_sort_items_with_one_key(self):
- items = self.get_test_items()
- sort_str = 'b'
- expect_items = [items[3], items[0], items[2], items[1]]
- self.assertEqual(expect_items, utils.sort_items(items, sort_str))
-
- def test_sort_items_with_multiple_keys(self):
- items = self.get_test_items()
- sort_str = 'a,b'
- expect_items = [items[0], items[1], items[3], items[2]]
- self.assertEqual(expect_items, utils.sort_items(items, sort_str))
-
- def test_sort_items_all_with_direction(self):
- items = self.get_test_items()
- sort_str = 'a:desc,b:desc'
- expect_items = [items[2], items[3], items[1], items[0]]
- self.assertEqual(expect_items, utils.sort_items(items, sort_str))
-
- def test_sort_items_some_with_direction(self):
- items = self.get_test_items()
- sort_str = 'a,b:desc'
- expect_items = [items[1], items[0], items[2], items[3]]
- self.assertEqual(expect_items, utils.sort_items(items, sort_str))
-
- def test_sort_items_with_object(self):
- item1 = mock.Mock(a=1, b=2)
- item2 = mock.Mock(a=1, b=3)
- item3 = mock.Mock(a=2, b=2)
- item4 = mock.Mock(a=2, b=1)
- items = [item1, item2, item3, item4]
- sort_str = 'b,a'
- expect_items = [item4, item1, item3, item2]
- self.assertEqual(expect_items, utils.sort_items(items, sort_str))
-
- def test_sort_items_with_empty_key(self):
- items = self.get_test_items()
- sort_srt = ''
- self.assertEqual(items, utils.sort_items(items, sort_srt))
- sort_srt = None
- self.assertEqual(items, utils.sort_items(items, sort_srt))
-
- def test_sort_items_with_invalid_key(self):
- items = self.get_test_items()
- sort_str = 'c'
- self.assertRaises(exceptions.CommandError,
- utils.sort_items,
- items, sort_str)
-
- def test_sort_items_with_invalid_direction(self):
- items = self.get_test_items()
- sort_str = 'a:bad_dir'
- self.assertRaises(exceptions.CommandError,
- utils.sort_items,
- items, sort_str)
-
- @mock.patch.object(time, 'sleep')
- def test_wait_for_status_ok(self, mock_sleep):
- # Tests the normal flow that the resource is status=active
- resource = mock.MagicMock(status='ACTIVE')
- status_f = mock.Mock(return_value=resource)
- res_id = str(uuid.uuid4())
- self.assertTrue(utils.wait_for_status(status_f, res_id,))
- mock_sleep.assert_not_called()
-
- @mock.patch.object(time, 'sleep')
- def test_wait_for_status_ok__with_overrides(self, mock_sleep):
- # Tests the normal flow that the resource is status=complete
- resource = mock.MagicMock(my_status='COMPLETE')
- status_f = mock.Mock(return_value=resource)
- res_id = str(uuid.uuid4())
- self.assertTrue(utils.wait_for_status(status_f, res_id,
- status_field='my_status',
- success_status=['complete']))
- mock_sleep.assert_not_called()
-
- @mock.patch.object(time, 'sleep')
- def test_wait_for_status_error(self, mock_sleep):
- # Tests that we fail if the resource is status=error
- resource = mock.MagicMock(status='ERROR')
- status_f = mock.Mock(return_value=resource)
- res_id = str(uuid.uuid4())
- self.assertFalse(utils.wait_for_status(status_f, res_id))
- mock_sleep.assert_not_called()
-
- @mock.patch.object(time, 'sleep')
- def test_wait_for_status_error_with_overrides(self, mock_sleep):
- # Tests that we fail if the resource is my_status=failed
- resource = mock.MagicMock(my_status='FAILED')
- status_f = mock.Mock(return_value=resource)
- res_id = str(uuid.uuid4())
- self.assertFalse(utils.wait_for_status(status_f, res_id,
- status_field='my_status',
- error_status=['failed']))
- mock_sleep.assert_not_called()
-
- @mock.patch.object(time, 'sleep')
- def test_wait_for_delete_ok(self, mock_sleep):
- # Tests the normal flow that the resource is deleted with a 404 coming
- # back on the 2nd iteration of the wait loop.
- resource = mock.MagicMock(status='ACTIVE', progress=None)
- mock_get = mock.Mock(side_effect=[resource,
- exceptions.NotFound(404)])
- manager = mock.MagicMock(get=mock_get)
- res_id = str(uuid.uuid4())
- callback = mock.Mock()
- self.assertTrue(utils.wait_for_delete(manager, res_id,
- callback=callback))
- mock_sleep.assert_called_once_with(5)
- callback.assert_called_once_with(0)
-
- @mock.patch.object(time, 'sleep')
- def test_wait_for_delete_timeout(self, mock_sleep):
- # Tests that we fail if the resource is not deleted before the timeout.
- resource = mock.MagicMock(status='ACTIVE')
- mock_get = mock.Mock(return_value=resource)
- manager = mock.MagicMock(get=mock_get)
- res_id = str(uuid.uuid4())
- self.assertFalse(utils.wait_for_delete(manager, res_id, sleep_time=1,
- timeout=1))
- mock_sleep.assert_called_once_with(1)
-
- @mock.patch.object(time, 'sleep')
- def test_wait_for_delete_error(self, mock_sleep):
- # Tests that we fail if the resource goes to error state while waiting.
- resource = mock.MagicMock(status='ERROR')
- mock_get = mock.Mock(return_value=resource)
- manager = mock.MagicMock(get=mock_get)
- res_id = str(uuid.uuid4())
- self.assertFalse(utils.wait_for_delete(manager, res_id))
- mock_sleep.assert_not_called()
-
- @mock.patch.object(time, 'sleep')
- def test_wait_for_delete_error_with_overrides(self, mock_sleep):
- # Tests that we fail if the resource is my_status=failed
- resource = mock.MagicMock(my_status='FAILED')
- mock_get = mock.Mock(return_value=resource)
- manager = mock.MagicMock(get=mock_get)
- res_id = str(uuid.uuid4())
- self.assertFalse(utils.wait_for_delete(manager, res_id,
- status_field='my_status',
- error_status=['failed']))
- mock_sleep.assert_not_called()
-
- @mock.patch.object(time, 'sleep')
- def test_wait_for_delete_error_with_overrides_exception(self, mock_sleep):
- # Tests that we succeed if the resource is specific exception
- mock_get = mock.Mock(side_effect=Exception)
- manager = mock.MagicMock(get=mock_get)
- res_id = str(uuid.uuid4())
- self.assertTrue(utils.wait_for_delete(manager, res_id,
- exception_name=['Exception']))
- mock_sleep.assert_not_called()
-
- def test_build_kwargs_dict_value_set(self):
- self.assertEqual({'arg_bla': 'bla'},
- utils.build_kwargs_dict('arg_bla', 'bla'))
-
- def test_build_kwargs_dict_value_None(self):
- self.assertEqual({}, utils.build_kwargs_dict('arg_bla', None))
-
- def test_build_kwargs_dict_value_empty_str(self):
- self.assertEqual({}, utils.build_kwargs_dict('arg_bla', ''))
-
-
-class NoUniqueMatch(Exception):
- pass
-
-
-class TestFindResource(test_utils.TestCase):
-
- def setUp(self):
- super(TestFindResource, self).setUp()
- self.name = 'legos'
- self.expected = mock.Mock()
- self.manager = mock.Mock()
- self.manager.resource_class = mock.Mock()
- self.manager.resource_class.__name__ = 'lego'
-
- def test_find_resource_get_int(self):
- self.manager.get = mock.Mock(return_value=self.expected)
- result = utils.find_resource(self.manager, 1)
- self.assertEqual(self.expected, result)
- self.manager.get.assert_called_with(1)
-
- def test_find_resource_get_int_string(self):
- self.manager.get = mock.Mock(return_value=self.expected)
- result = utils.find_resource(self.manager, "2")
- self.assertEqual(self.expected, result)
- self.manager.get.assert_called_with(2)
-
- def test_find_resource_get_uuid(self):
- uuid = '9a0dc2a0-ad0d-11e3-a5e2-0800200c9a66'
- self.manager.get = mock.Mock(return_value=self.expected)
- result = utils.find_resource(self.manager, uuid)
- self.assertEqual(self.expected, result)
- self.manager.get.assert_called_with(uuid)
-
- def test_find_resource_get_whatever(self):
- self.manager.get = mock.Mock(return_value=self.expected)
- result = utils.find_resource(self.manager, 'whatever')
- self.assertEqual(self.expected, result)
- self.manager.get.assert_called_with('whatever')
-
- def test_find_resource_find(self):
- self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
- self.manager.find = mock.Mock(return_value=self.expected)
- result = utils.find_resource(self.manager, self.name)
- self.assertEqual(self.expected, result)
- self.manager.get.assert_called_with(self.name)
- self.manager.find.assert_called_with(name=self.name)
-
- def test_find_resource_find_not_found(self):
- self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
- self.manager.find = mock.Mock(
- side_effect=exceptions.NotFound(404, "2")
- )
- result = self.assertRaises(exceptions.CommandError,
- utils.find_resource,
- self.manager,
- self.name)
- self.assertEqual("No lego with a name or ID of 'legos' exists.",
- str(result))
- self.manager.get.assert_called_with(self.name)
- self.manager.find.assert_called_with(name=self.name)
-
- def test_find_resource_list_forbidden(self):
- self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
- self.manager.find = mock.Mock(side_effect=Exception('Boom!'))
- self.manager.list = mock.Mock(
- side_effect=exceptions.Forbidden(403)
- )
- self.assertRaises(exceptions.Forbidden,
- utils.find_resource,
- self.manager,
- self.name)
- self.manager.list.assert_called_with()
-
- def test_find_resource_find_no_unique(self):
- self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
- self.manager.find = mock.Mock(side_effect=NoUniqueMatch())
- result = self.assertRaises(exceptions.CommandError,
- utils.find_resource,
- self.manager,
- self.name)
- self.assertEqual("More than one lego exists with the name 'legos'.",
- str(result))
- self.manager.get.assert_called_with(self.name)
- self.manager.find.assert_called_with(name=self.name)
-
- def test_find_resource_silly_resource(self):
- # We need a resource with no resource_class for this test, start fresh
- self.manager = mock.Mock()
- self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
- self.manager.find = mock.Mock(
- side_effect=AttributeError(
- "'Controller' object has no attribute 'find'",
- )
- )
- silly_resource = FakeOddballResource(
- None,
- {'id': '12345', 'name': self.name},
- loaded=True,
- )
- self.manager.list = mock.Mock(
- return_value=[silly_resource, ],
- )
- result = utils.find_resource(self.manager, self.name)
- self.assertEqual(silly_resource, result)
- self.manager.get.assert_called_with(self.name)
- self.manager.find.assert_called_with(name=self.name)
-
- def test_find_resource_silly_resource_not_found(self):
- # We need a resource with no resource_class for this test, start fresh
- self.manager = mock.Mock()
- self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
- self.manager.find = mock.Mock(
- side_effect=AttributeError(
- "'Controller' object has no attribute 'find'",
- )
- )
- self.manager.list = mock.Mock(return_value=[])
- result = self.assertRaises(exceptions.CommandError,
- utils.find_resource,
- self.manager,
- self.name)
- self.assertEqual("Could not find resource legos",
- str(result))
- self.manager.get.assert_called_with(self.name)
- self.manager.find.assert_called_with(name=self.name)
-
- def test_format_dict(self):
- expected = "a='b', c='d', e='f'"
- self.assertEqual(expected,
- utils.format_dict({'a': 'b', 'c': 'd', 'e': 'f'}))
- self.assertEqual(expected,
- utils.format_dict({'e': 'f', 'c': 'd', 'a': 'b'}))
-
- def test_format_list(self):
- expected = 'a, b, c'
- self.assertEqual(expected, utils.format_list(['a', 'b', 'c']))
- self.assertEqual(expected, utils.format_list(['c', 'b', 'a']))
-
- def test_format_list_of_dicts(self):
- expected = "a='b', c='d'\ne='f'"
- sorted_data = [{'a': 'b', 'c': 'd'}, {'e': 'f'}]
- unsorted_data = [{'c': 'd', 'a': 'b'}, {'e': 'f'}]
- self.assertEqual(expected, utils.format_list_of_dicts(sorted_data))
- self.assertEqual(expected, utils.format_list_of_dicts(unsorted_data))
- self.assertEqual('', utils.format_list_of_dicts([]))
- self.assertEqual('', utils.format_list_of_dicts([{}]))
-
- def test_format_list_separator(self):
- expected = 'a\nb\nc'
- actual_pre_sorted = utils.format_list(['a', 'b', 'c'], separator='\n')
- actual_unsorted = utils.format_list(['c', 'b', 'a'], separator='\n')
- self.assertEqual(expected, actual_pre_sorted)
- self.assertEqual(expected, actual_unsorted)
diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py
index c9e2025..85c11c9 100644
--- a/openstackclient/tests/compute/v2/fakes.py
+++ b/openstackclient/tests/compute/v2/fakes.py
@@ -24,26 +24,6 @@ from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils
from openstackclient.tests.volume.v2 import fakes as volume_fakes
-
-extension_name = 'Multinic'
-extension_namespace = 'http://docs.openstack.org/compute/ext/'\
- 'multinic/api/v1.1'
-extension_description = 'Multiple network support'
-extension_updated = '2014-01-07T12:00:0-00:00'
-extension_alias = 'NMN'
-extension_links = '[{"href":'\
- '"https://github.com/openstack/compute-api", "type":'\
- ' "text/html", "rel": "describedby"}]'
-
-EXTENSION = {
- 'name': extension_name,
- 'namespace': extension_namespace,
- 'description': extension_description,
- 'updated': extension_updated,
- 'alias': extension_alias,
- 'links': extension_links,
-}
-
floating_ip_num = 100
fix_ip_num = 100
injected_file_num = 100
@@ -56,6 +36,8 @@ instance_num = 10
property_num = 128
secgroup_rule_num = 20
secgroup_num = 10
+servgroup_num = 10
+servgroup_members_num = 10
project_name = 'project_test'
QUOTA = {
'project': project_name,
@@ -71,6 +53,8 @@ QUOTA = {
'properties': property_num,
'secgroup_rules': secgroup_rule_num,
'secgroups': secgroup_num,
+ 'server-groups': servgroup_num,
+ 'server-group-members': servgroup_members_num
}
QUOTA_columns = tuple(sorted(QUOTA))
@@ -101,12 +85,51 @@ class FakeAggregate(object):
"availability_zone": "ag_zone",
}
}
+
+ # Overwrite default attributes.
aggregate_info.update(attrs)
+
aggregate = fakes.FakeResource(
info=copy.deepcopy(aggregate_info),
loaded=True)
return aggregate
+ @staticmethod
+ def create_aggregates(attrs=None, count=2):
+ """Create multiple fake aggregates.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of aggregates to fake
+ :return:
+ A list of FakeResource objects faking the aggregates
+ """
+ aggregates = []
+ for i in range(0, count):
+ aggregates.append(FakeAggregate.create_one_aggregate(attrs))
+
+ return aggregates
+
+ @staticmethod
+ def get_aggregates(aggregates=None, count=2):
+ """Get an iterable MagicMock object with a list of faked aggregates.
+
+ If aggregates list is provided, then initialize the Mock object
+ with the list. Otherwise create one.
+
+ :param List aggregates:
+ A list of FakeResource objects faking aggregates
+ :param int count:
+ The number of aggregates to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ aggregates
+ """
+ if aggregates is None:
+ aggregates = FakeAggregate.create_aggregates(count)
+ return mock.MagicMock(side_effect=aggregates)
+
class FakeComputev2Client(object):
@@ -162,6 +185,9 @@ class FakeComputev2Client(object):
self.floating_ips = mock.Mock()
self.floating_ips.resource_class = fakes.FakeResource(None, {})
+ self.floating_ip_pools = mock.Mock()
+ self.floating_ip_pools.resource_class = fakes.FakeResource(None, {})
+
self.networks = mock.Mock()
self.networks.resource_class = fakes.FakeResource(None, {})
@@ -235,6 +261,8 @@ class FakeAgent(object):
'md5hash': 'agent-md5hash',
'hypervisor': 'hypervisor',
}
+
+ # Overwrite default attributes.
agent_info.update(attrs)
agent = fakes.FakeResource(info=copy.deepcopy(agent_info),
@@ -259,6 +287,42 @@ class FakeAgent(object):
return agents
+class FakeExtension(object):
+ """Fake one or more extension."""
+
+ @staticmethod
+ def create_one_extension(attrs=None):
+ """Create a fake extension.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object with name, namespace, etc.
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ extension_info = {
+ 'name': 'name-' + uuid.uuid4().hex,
+ 'namespace': (
+ 'http://docs.openstack.org/compute/ext/multinic/api/v1.1'),
+ 'description': 'description-' + uuid.uuid4().hex,
+ 'updated': '2014-01-07T12:00:0-00:00',
+ 'alias': 'NMN',
+ 'links': ('[{"href":'
+ '"https://github.com/openstack/compute-api", "type":'
+ ' "text/html", "rel": "describedby"}]')
+ }
+
+ # Overwrite default attributes.
+ extension_info.update(attrs)
+
+ extension = fakes.FakeResource(
+ info=copy.deepcopy(extension_info),
+ loaded=True)
+ return extension
+
+
class FakeHypervisor(object):
"""Fake one or more hypervisor."""
@@ -338,7 +402,7 @@ class FakeHypervisorStats(object):
:param Dictionary attrs:
A dictionary with all attributes
:return:
- A FakeResource object, with id, hypervisor_hostname, and so on
+ A FakeResource object, with count, current_workload, and so on
"""
attrs = attrs or {}
@@ -357,6 +421,8 @@ class FakeHypervisorStats(object):
'vcpus': 8,
'vcpus_used': 3,
}
+
+ # Overwrite default attributes.
stats_info.update(attrs)
# Set default method.
@@ -436,6 +502,25 @@ class FakeSecurityGroup(object):
return security_groups
+ @staticmethod
+ def get_security_groups(security_groups=None, count=2):
+ """Get an iterable MagicMock object with a list of faked security groups.
+
+ If security groups list is provided, then initialize the Mock object
+ with the list. Otherwise create one.
+
+ :param List security groups:
+ A list of FakeResource objects faking security groups
+ :param int count:
+ The number of security groups to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ security groups
+ """
+ if security_groups is None:
+ security_groups = FakeSecurityGroup.create_security_groups(count)
+ return mock.MagicMock(side_effect=security_groups)
+
class FakeSecurityGroupRule(object):
"""Fake one or more security group rules."""
@@ -501,7 +586,7 @@ class FakeServer(object):
:param Dictionary methods:
A dictionary with all methods
:return:
- A FakeResource object, with id, name, metadata
+ A FakeResource object, with id, name, metadata, and so on
"""
attrs = attrs or {}
methods = methods or {}
@@ -577,15 +662,19 @@ class FakeService(object):
:param Dictionary attrs:
A dictionary with all attributes
:return:
- A FakeResource object, with id, name, ram, vcpus, properties
+ A FakeResource object, with id, host, binary, and so on
"""
attrs = attrs or {}
# Set default attributes.
service_info = {
+ 'id': 'id-' + uuid.uuid4().hex,
'host': 'host-' + uuid.uuid4().hex,
'binary': 'binary-' + uuid.uuid4().hex,
'status': 'enabled',
+ 'zone': 'zone-' + uuid.uuid4().hex,
+ 'state': 'state-' + uuid.uuid4().hex,
+ 'updated_at': 'time-' + uuid.uuid4().hex,
'disabled_reason': 'earthquake',
}
@@ -625,7 +714,7 @@ class FakeFlavor(object):
:param Dictionary attrs:
A dictionary with all attributes
:return:
- A FakeResource object, with id, name, ram, vcpus, properties
+ A FakeResource object, with id, name, ram, vcpus, and so on
"""
attrs = attrs or {}
@@ -641,6 +730,7 @@ class FakeFlavor(object):
'OS-FLV-DISABLED:disabled': False,
'os-flavor-access:is_public': True,
'OS-FLV-EXT-DATA:ephemeral': 0,
+ 'properties': {'property': 'value'},
}
# Overwrite default attributes.
@@ -697,10 +787,39 @@ class FakeFlavor(object):
flavors
"""
if flavors is None:
- flavors = FakeServer.create_flavors(count)
+ flavors = FakeFlavor.create_flavors(count)
return mock.MagicMock(side_effect=flavors)
+class FakeFlavorAccess(object):
+ """Fake one or more flavor accesses."""
+
+ @staticmethod
+ def create_one_flavor_access(attrs=None):
+ """Create a fake flavor access.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with flavor_id, tenat_id
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ flavor_access_info = {
+ 'flavor_id': 'flavor-id-' + uuid.uuid4().hex,
+ 'tenant_id': 'tenant-id-' + uuid.uuid4().hex,
+ }
+
+ # Overwrite default attributes.
+ flavor_access_info.update(attrs)
+
+ flavor_access = fakes.FakeResource(
+ info=copy.deepcopy(flavor_access_info), loaded=True)
+
+ return flavor_access
+
+
class FakeKeypair(object):
"""Fake one or more keypairs."""
@@ -711,7 +830,7 @@ class FakeKeypair(object):
:param Dictionary attrs:
A dictionary with all attributes
:return:
- A FakeResource
+ A FakeResource object, name, fingerprint, and so on
"""
attrs = attrs or {}
@@ -751,6 +870,25 @@ class FakeKeypair(object):
return keypairs
+ @staticmethod
+ def get_keypairs(keypairs=None, count=2):
+ """Get an iterable MagicMock object with a list of faked keypairs.
+
+ If keypairs list is provided, then initialize the Mock object with the
+ list. Otherwise create one.
+
+ :param List keypairs:
+ A list of FakeResource objects faking keypairs
+ :param int count:
+ The number of keypairs to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ keypairs
+ """
+ if keypairs is None:
+ keypairs = FakeKeypair.create_keypairs(count)
+ return mock.MagicMock(side_effect=keypairs)
+
class FakeAvailabilityZone(object):
"""Fake one or more compute availability zones (AZs)."""
@@ -876,6 +1014,54 @@ class FakeFloatingIP(object):
return mock.MagicMock(side_effect=floating_ips)
+class FakeFloatingIPPool(object):
+ """Fake one or more floating ip pools."""
+
+ @staticmethod
+ def create_one_floating_ip_pool(attrs=None):
+ """Create a fake floating ip pool.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with name, etc
+ """
+ if attrs is None:
+ attrs = {}
+
+ # Set default attributes.
+ floating_ip_pool_attrs = {
+ 'name': 'floating-ip-pool-name-' + uuid.uuid4().hex,
+ }
+
+ # Overwrite default attributes.
+ floating_ip_pool_attrs.update(attrs)
+
+ floating_ip_pool = fakes.FakeResource(
+ info=copy.deepcopy(floating_ip_pool_attrs),
+ loaded=True)
+
+ return floating_ip_pool
+
+ @staticmethod
+ def create_floating_ip_pools(attrs=None, count=2):
+ """Create multiple fake floating ip pools.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of floating ip pools to fake
+ :return:
+ A list of FakeResource objects faking the floating ip pools
+ """
+ floating_ip_pools = []
+ for i in range(0, count):
+ floating_ip_pools.append(
+ FakeFloatingIPPool.create_one_floating_ip_pool(attrs)
+ )
+ return floating_ip_pools
+
+
class FakeNetwork(object):
"""Fake one or more networks."""
@@ -981,13 +1167,12 @@ class FakeHost(object):
:param Dictionary attrs:
A dictionary with all attributes
:return:
- A FakeResource object, with id and other attributes
+ A FakeResource object, with uuid and other attributes
"""
attrs = attrs or {}
# Set default attributes.
host_info = {
- "id": 1,
"service_id": 1,
"host": "host1",
"uuid": 'host-id-' + uuid.uuid4().hex,
@@ -1014,7 +1199,13 @@ class FakeHost(object):
"stats": "",
"numa_topology": "",
"ram_allocation_ratio": 1.0,
- "cpu_allocation_ratio": 1.0
+ "cpu_allocation_ratio": 1.0,
+ "zone": 'zone-' + uuid.uuid4().hex,
+ "host_name": 'name-' + uuid.uuid4().hex,
+ "service": 'service-' + uuid.uuid4().hex,
+ "cpu": 4,
+ "disk_gb": 100,
+ 'project': 'project-' + uuid.uuid4().hex,
}
host_info.update(attrs)
host = fakes.FakeResource(
@@ -1038,6 +1229,7 @@ class FakeServerGroup(object):
if attrs is None:
attrs = {}
+ # Set default attributes.
server_group_info = {
'id': 'server-group-id-' + uuid.uuid4().hex,
'members': [],
@@ -1047,7 +1239,10 @@ class FakeServerGroup(object):
'project_id': 'server-group-project-id-' + uuid.uuid4().hex,
'user_id': 'server-group-user-id-' + uuid.uuid4().hex,
}
+
+ # Overwrite default attributes.
server_group_info.update(attrs)
+
server_group = fakes.FakeResource(
info=copy.deepcopy(server_group_info),
loaded=True)
diff --git a/openstackclient/tests/compute/v2/test_agent.py b/openstackclient/tests/compute/v2/test_agent.py
index d3d1ff2..07265bb 100644
--- a/openstackclient/tests/compute/v2/test_agent.py
+++ b/openstackclient/tests/compute/v2/test_agent.py
@@ -14,16 +14,20 @@
#
import mock
-
from mock import call
-from openstackclient.common import exceptions
+
+from osc_lib import exceptions
+
from openstackclient.compute.v2 import agent
from openstackclient.tests.compute.v2 import fakes as compute_fakes
+from openstackclient.tests import utils as tests_utils
class TestAgent(compute_fakes.TestComputev2):
- fake_agent = compute_fakes.FakeAgent.create_one_agent()
+ attr = {}
+ attr['agent_id'] = 1
+ fake_agent = compute_fakes.FakeAgent.create_one_agent(attr)
columns = (
'agent_id',
@@ -160,6 +164,15 @@ class TestAgentDelete(TestAgent):
]
self.agents_mock.delete.assert_has_calls(calls)
+ def test_agent_delete_no_input(self):
+ arglist = []
+ verifylist = None
+ self.assertRaises(tests_utils.ParserException,
+ self.check_parser,
+ self.cmd,
+ arglist,
+ verifylist)
+
class TestAgentList(TestAgent):
@@ -227,21 +240,34 @@ class TestAgentSet(TestAgent):
super(TestAgentSet, self).setUp()
self.agents_mock.update.return_value = self.fake_agent
+ self.agents_mock.list.return_value = [self.fake_agent]
self.cmd = agent.SetAgent(self.app, None)
- def test_agent_set(self):
+ def test_agent_set_nothing(self):
+ arglist = [
+ '1',
+ ]
+ verifylist = [
+ ('id', '1'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.agents_mock.update.assert_called_with(parsed_args.id,
+ self.fake_agent.version,
+ self.fake_agent.url,
+ self.fake_agent.md5hash)
+ self.assertIsNone(result)
+
+ def test_agent_set_version(self):
arglist = [
- 'id',
- 'new-version',
- 'new-url',
- 'new-md5hash',
+ '1',
+ '--agent-version', 'new-version',
]
verifylist = [
- ('id', 'id'),
+ ('id', '1'),
('version', 'new-version'),
- ('url', 'new-url'),
- ('md5hash', 'new-md5hash'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -249,6 +275,46 @@ class TestAgentSet(TestAgent):
self.agents_mock.update.assert_called_with(parsed_args.id,
parsed_args.version,
+ self.fake_agent.url,
+ self.fake_agent.md5hash)
+ self.assertIsNone(result)
+
+ def test_agent_set_url(self):
+ arglist = [
+ '1',
+ '--url', 'new-url',
+ ]
+
+ verifylist = [
+ ('id', '1'),
+ ('url', 'new-url'),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.agents_mock.update.assert_called_with(parsed_args.id,
+ self.fake_agent.version,
parsed_args.url,
+ self.fake_agent.md5hash)
+ self.assertIsNone(result)
+
+ def test_agent_set_md5hash(self):
+ arglist = [
+ '1',
+ '--md5hash', 'new-md5hash',
+ ]
+
+ verifylist = [
+ ('id', '1'),
+ ('md5hash', 'new-md5hash'),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.agents_mock.update.assert_called_with(parsed_args.id,
+ self.fake_agent.version,
+ self.fake_agent.url,
parsed_args.md5hash)
self.assertIsNone(result)
diff --git a/openstackclient/tests/compute/v2/test_aggregate.py b/openstackclient/tests/compute/v2/test_aggregate.py
index 58dd775..3ebca35 100644
--- a/openstackclient/tests/compute/v2/test_aggregate.py
+++ b/openstackclient/tests/compute/v2/test_aggregate.py
@@ -13,6 +13,12 @@
# under the License.
#
+import mock
+from mock import call
+
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.compute.v2 import aggregate
from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests import utils as tests_utils
@@ -135,25 +141,74 @@ class TestAggregateCreate(TestAggregate):
class TestAggregateDelete(TestAggregate):
+ fake_ags = compute_fakes.FakeAggregate.create_aggregates(count=2)
+
def setUp(self):
super(TestAggregateDelete, self).setUp()
- self.aggregate_mock.get.return_value = self.fake_ag
+ self.aggregate_mock.get = (
+ compute_fakes.FakeAggregate.get_aggregates(self.fake_ags))
self.cmd = aggregate.DeleteAggregate(self.app, None)
def test_aggregate_delete(self):
arglist = [
- 'ag1',
+ self.fake_ags[0].id
]
verifylist = [
- ('aggregate', 'ag1'),
+ ('aggregate', [self.fake_ags[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
- self.aggregate_mock.delete.assert_called_once_with(self.fake_ag.id)
+ self.aggregate_mock.get.assert_called_once_with(self.fake_ags[0].id)
+ self.aggregate_mock.delete.assert_called_once_with(self.fake_ags[0].id)
self.assertIsNone(result)
+ def test_delete_multiple_aggregates(self):
+ arglist = []
+ for a in self.fake_ags:
+ arglist.append(a.id)
+ verifylist = [
+ ('aggregate', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for a in self.fake_ags:
+ calls.append(call(a.id))
+ self.aggregate_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_delete_multiple_agggregates_with_exception(self):
+ arglist = [
+ self.fake_ags[0].id,
+ 'unexist_aggregate',
+ ]
+ verifylist = [
+ ('aggregate', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self.fake_ags[0], exceptions.CommandError]
+ with mock.patch.object(utils, 'find_resource',
+ side_effect=find_mock_result) as find_mock:
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 aggregates failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(self.aggregate_mock, self.fake_ags[0].id)
+ find_mock.assert_any_call(self.aggregate_mock, 'unexist_aggregate')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.aggregate_mock.delete.assert_called_once_with(
+ self.fake_ags[0].id
+ )
+
class TestAggregateList(TestAggregate):
diff --git a/openstackclient/tests/compute/v2/test_console.py b/openstackclient/tests/compute/v2/test_console.py
new file mode 100644
index 0000000..fe4f26a
--- /dev/null
+++ b/openstackclient/tests/compute/v2/test_console.py
@@ -0,0 +1,196 @@
+# Copyright 2016 Huawei, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import mock
+
+from openstackclient.compute.v2 import console
+from openstackclient.tests.compute.v2 import fakes as compute_fakes
+
+
+class TestConsole(compute_fakes.TestComputev2):
+
+ def setUp(self):
+ super(TestConsole, self).setUp()
+ self.servers_mock = self.app.client_manager.compute.servers
+ self.servers_mock.reset_mock()
+
+
+class TestConsoleUrlShow(TestConsole):
+
+ def setUp(self):
+ super(TestConsoleUrlShow, self).setUp()
+ fake_console_data = {'remote_console': {'url': 'http://localhost',
+ 'protocol': 'fake_protocol',
+ 'type': 'fake_type'}}
+ methods = {
+ 'get_vnc_console': fake_console_data,
+ 'get_spice_console': fake_console_data,
+ 'get_serial_console': fake_console_data,
+ 'get_rdp_console': fake_console_data,
+ 'get_mks_console': fake_console_data,
+ }
+ self.fake_server = compute_fakes.FakeServer.create_one_server(
+ methods=methods)
+ self.servers_mock.get.return_value = self.fake_server
+
+ self.columns = (
+ 'protocol',
+ 'type',
+ 'url',
+ )
+ self.data = (
+ fake_console_data['remote_console']['protocol'],
+ fake_console_data['remote_console']['type'],
+ fake_console_data['remote_console']['url']
+ )
+
+ self.cmd = console.ShowConsoleURL(self.app, None)
+
+ def test_console_url_show_by_default(self):
+ arglist = [
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('url_type', 'novnc'),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.fake_server.get_vnc_console.assert_called_once_with('novnc')
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_console_url_show_with_novnc(self):
+ arglist = [
+ '--novnc',
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('url_type', 'novnc'),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.fake_server.get_vnc_console.assert_called_once_with('novnc')
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_console_url_show_with_xvpvnc(self):
+ arglist = [
+ '--xvpvnc',
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('url_type', 'xvpvnc'),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.fake_server.get_vnc_console.assert_called_once_with('xvpvnc')
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_console_url_show_with_spice(self):
+ arglist = [
+ '--spice',
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('url_type', 'spice-html5'),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.fake_server.get_spice_console.assert_called_once_with(
+ 'spice-html5')
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_console_url_show_compatible(self):
+ methods = {
+ 'get_vnc_console': {'console': {'url': 'http://localhost',
+ 'type': 'fake_type'}},
+ }
+ old_fake_server = compute_fakes.FakeServer.create_one_server(
+ methods=methods)
+ old_columns = (
+ 'type',
+ 'url',
+ )
+ old_data = (
+ methods['get_vnc_console']['console']['type'],
+ methods['get_vnc_console']['console']['url']
+ )
+ arglist = [
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('url_type', 'novnc'),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ with mock.patch.object(self.servers_mock, 'get',
+ return_value=old_fake_server):
+ columns, data = self.cmd.take_action(parsed_args)
+ old_fake_server.get_vnc_console.assert_called_once_with('novnc')
+ self.assertEqual(old_columns, columns)
+ self.assertEqual(old_data, data)
+
+ def test_console_url_show_with_rdp(self):
+ arglist = [
+ '--rdp',
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('url_type', 'rdp-html5'),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.fake_server.get_rdp_console.assert_called_once_with(
+ 'rdp-html5')
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_console_url_show_with_serial(self):
+ arglist = [
+ '--serial',
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('url_type', 'serial'),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.fake_server.get_serial_console.assert_called_once_with(
+ 'serial')
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_console_url_show_with_mks(self):
+ arglist = [
+ '--mks',
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('url_type', 'webmks'),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.fake_server.get_mks_console.assert_called_once_with()
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py
index 434d5f9..d326ea8 100644
--- a/openstackclient/tests/compute/v2/test_flavor.py
+++ b/openstackclient/tests/compute/v2/test_flavor.py
@@ -13,13 +13,14 @@
# under the License.
#
-import copy
+import mock
+from mock import call
+
+from osc_lib import exceptions
+from osc_lib import utils
-from openstackclient.common import exceptions
-from openstackclient.common import utils
from openstackclient.compute.v2 import flavor
from openstackclient.tests.compute.v2 import fakes as compute_fakes
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
from openstackclient.tests import utils as tests_utils
@@ -45,7 +46,7 @@ class TestFlavorCreate(TestFlavor):
flavor = compute_fakes.FakeFlavor.create_one_flavor(
attrs={'links': 'flavor-links'})
-
+ project = identity_fakes.FakeProject.create_one_project()
columns = (
'OS-FLV-DISABLED:disabled',
'OS-FLV-EXT-DATA:ephemeral',
@@ -53,6 +54,7 @@ class TestFlavorCreate(TestFlavor):
'id',
'name',
'os-flavor-access:is_public',
+ 'properties',
'ram',
'rxtx_factor',
'swap',
@@ -65,6 +67,7 @@ class TestFlavorCreate(TestFlavor):
flavor.id,
flavor.name,
flavor.is_public,
+ utils.format_dict(flavor.properties),
flavor.ram,
flavor.rxtx_factor,
flavor.swap,
@@ -74,6 +77,8 @@ class TestFlavorCreate(TestFlavor):
def setUp(self):
super(TestFlavorCreate, self).setUp()
+ # Return a project
+ self.projects_mock.get.return_value = self.project
self.flavors_mock.create.return_value = self.flavor
self.cmd = flavor.CreateFlavor(self.app, None)
@@ -107,7 +112,6 @@ class TestFlavorCreate(TestFlavor):
def test_flavor_create_all_options(self):
arglist = [
- self.flavor.name,
'--id', self.flavor.id,
'--ram', str(self.flavor.ram),
'--disk', str(self.flavor.disk),
@@ -116,9 +120,10 @@ class TestFlavorCreate(TestFlavor):
'--vcpus', str(self.flavor.vcpus),
'--rxtx-factor', str(self.flavor.rxtx_factor),
'--public',
+ '--property', 'property=value',
+ self.flavor.name,
]
verifylist = [
- ('name', self.flavor.name),
('id', self.flavor.id),
('ram', self.flavor.ram),
('disk', self.flavor.disk),
@@ -127,6 +132,8 @@ class TestFlavorCreate(TestFlavor):
('vcpus', self.flavor.vcpus),
('rxtx_factor', self.flavor.rxtx_factor),
('public', True),
+ ('property', {'property': 'value'}),
+ ('name', self.flavor.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -143,6 +150,8 @@ class TestFlavorCreate(TestFlavor):
)
columns, data = self.cmd.take_action(parsed_args)
self.flavors_mock.create.assert_called_once_with(*args)
+ self.flavor.set_keys.assert_called_once_with({'property': 'value'})
+ self.flavor.get_keys.assert_called_once_with()
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -151,7 +160,6 @@ class TestFlavorCreate(TestFlavor):
self.flavor.is_public = False
arglist = [
- self.flavor.name,
'--id', self.flavor.id,
'--ram', str(self.flavor.ram),
'--disk', str(self.flavor.disk),
@@ -160,9 +168,12 @@ class TestFlavorCreate(TestFlavor):
'--vcpus', str(self.flavor.vcpus),
'--rxtx-factor', str(self.flavor.rxtx_factor),
'--private',
+ '--project', self.project.id,
+ '--property', 'key1=value1',
+ '--property', 'key2=value2',
+ self.flavor.name,
]
verifylist = [
- ('name', self.flavor.name),
('id', self.flavor.id),
('ram', self.flavor.ram),
('disk', self.flavor.disk),
@@ -171,6 +182,9 @@ class TestFlavorCreate(TestFlavor):
('vcpus', self.flavor.vcpus),
('rxtx_factor', self.flavor.rxtx_factor),
('public', False),
+ ('project', self.project.id),
+ ('property', {'key1': 'value1', 'key2': 'value2'}),
+ ('name', self.flavor.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -187,10 +201,31 @@ class TestFlavorCreate(TestFlavor):
)
columns, data = self.cmd.take_action(parsed_args)
self.flavors_mock.create.assert_called_once_with(*args)
-
+ self.flavor_access_mock.add_tenant_access.assert_called_with(
+ self.flavor.id,
+ self.project.id,
+ )
+ self.flavor.set_keys.assert_called_with(
+ {'key1': 'value1', 'key2': 'value2'})
+ self.flavor.get_keys.assert_called_with()
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_public_flavor_create_with_project(self):
+ arglist = [
+ '--project', self.project.id,
+ self.flavor.name,
+ ]
+ verifylist = [
+ ('project', self.project.id),
+ ('name', self.flavor.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+
def test_flavor_create_no_options(self):
arglist = []
verifylist = None
@@ -203,47 +238,73 @@ class TestFlavorCreate(TestFlavor):
class TestFlavorDelete(TestFlavor):
- flavor = compute_fakes.FakeFlavor.create_one_flavor()
+ flavors = compute_fakes.FakeFlavor.create_flavors(count=2)
def setUp(self):
super(TestFlavorDelete, self).setUp()
- self.flavors_mock.get.return_value = self.flavor
+ self.flavors_mock.get = (
+ compute_fakes.FakeFlavor.get_flavors(self.flavors))
self.flavors_mock.delete.return_value = None
self.cmd = flavor.DeleteFlavor(self.app, None)
def test_flavor_delete(self):
arglist = [
- self.flavor.id
+ self.flavors[0].id
]
verifylist = [
- ('flavor', self.flavor.id),
+ ('flavor', [self.flavors[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.flavors_mock.delete.assert_called_with(self.flavor.id)
+ self.flavors_mock.delete.assert_called_with(self.flavors[0].id)
self.assertIsNone(result)
- def test_flavor_delete_with_unexist_flavor(self):
- self.flavors_mock.get.side_effect = exceptions.NotFound(None)
- self.flavors_mock.find.side_effect = exceptions.NotFound(None)
+ def test_delete_multiple_flavors(self):
+ arglist = []
+ for f in self.flavors:
+ arglist.append(f.id)
+ verifylist = [
+ ('flavor', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for f in self.flavors:
+ calls.append(call(f.id))
+ self.flavors_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_multi_flavors_delete_with_exception(self):
arglist = [
- 'unexist_flavor'
+ self.flavors[0].id,
+ 'unexist_flavor',
]
verifylist = [
- ('flavor', 'unexist_flavor'),
+ ('flavor', [self.flavors[0].id, 'unexist_flavor'])
]
-
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.assertRaises(
- exceptions.CommandError,
- self.cmd.take_action,
- parsed_args)
+ find_mock_result = [self.flavors[0], exceptions.CommandError]
+ self.flavors_mock.get = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+ self.flavors_mock.find.side_effect = exceptions.NotFound(None)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 flavors failed to delete.', str(e))
+
+ self.flavors_mock.get.assert_any_call(self.flavors[0].id)
+ self.flavors_mock.get.assert_any_call('unexist_flavor')
+ self.flavors_mock.delete.assert_called_once_with(self.flavors[0].id)
class TestFlavorList(TestFlavor):
@@ -440,6 +501,7 @@ class TestFlavorSet(TestFlavor):
# Return value of self.flavors_mock.find().
flavor = compute_fakes.FakeFlavor.create_one_flavor(
attrs={'os-flavor-access:is_public': False})
+ project = identity_fakes.FakeProject.create_one_project()
def setUp(self):
super(TestFlavorSet, self).setUp()
@@ -447,11 +509,7 @@ class TestFlavorSet(TestFlavor):
self.flavors_mock.find.return_value = self.flavor
self.flavors_mock.get.side_effect = exceptions.NotFound(None)
# Return a project
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
self.cmd = flavor.SetFlavor(self.app, None)
def test_flavor_set_property(self):
@@ -468,26 +526,30 @@ class TestFlavorSet(TestFlavor):
result = self.cmd.take_action(parsed_args)
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
is_public=None)
+ self.flavor.set_keys.assert_called_with({'FOO': '"B A R"'})
self.assertIsNone(result)
def test_flavor_set_project(self):
arglist = [
- '--project', identity_fakes.project_id,
+ '--project', self.project.id,
self.flavor.id,
]
verifylist = [
- ('project', identity_fakes.project_id),
+ ('project', self.project.id),
('flavor', self.flavor.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.assertIsNone(result)
+ self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
+ is_public=None)
self.flavor_access_mock.add_tenant_access.assert_called_with(
self.flavor.id,
- identity_fakes.project_id,
+ self.project.id,
)
+ self.flavor.set_keys.assert_not_called()
+ self.assertIsNone(result)
def test_flavor_set_no_project(self):
arglist = [
@@ -495,7 +557,7 @@ class TestFlavorSet(TestFlavor):
self.flavor.id,
]
verifylist = [
- ('project', ''),
+ ('project', None),
('flavor', self.flavor.id),
]
self.assertRaises(tests_utils.ParserException, self.check_parser,
@@ -503,28 +565,24 @@ class TestFlavorSet(TestFlavor):
def test_flavor_set_no_flavor(self):
arglist = [
- '--project', identity_fakes.project_id,
+ '--project', self.project.id,
]
verifylist = [
- ('project', identity_fakes.project_id),
+ ('project', self.project.id),
]
-
- self.assertRaises(tests_utils.ParserException,
- self.check_parser,
- self.cmd,
- arglist,
- verifylist)
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
def test_flavor_set_with_unexist_flavor(self):
self.flavors_mock.get.side_effect = exceptions.NotFound(None)
self.flavors_mock.find.side_effect = exceptions.NotFound(None)
arglist = [
- '--project', identity_fakes.project_id,
+ '--project', self.project.id,
'unexist_flavor',
]
verifylist = [
- ('project', identity_fakes.project_id),
+ ('project', self.project.id),
('flavor', 'unexist_flavor'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -540,20 +598,25 @@ class TestFlavorSet(TestFlavor):
verifylist = [
('flavor', self.flavor.id),
]
-
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.assertRaises(exceptions.CommandError, self.cmd.take_action,
- parsed_args)
+ result = self.cmd.take_action(parsed_args)
+
+ self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
+ is_public=None)
+ self.flavor_access_mock.add_tenant_access.assert_not_called()
+ self.assertIsNone(result)
class TestFlavorShow(TestFlavor):
# Return value of self.flavors_mock.find().
+ flavor_access = compute_fakes.FakeFlavorAccess.create_one_flavor_access()
flavor = compute_fakes.FakeFlavor.create_one_flavor()
columns = (
'OS-FLV-DISABLED:disabled',
'OS-FLV-EXT-DATA:ephemeral',
+ 'access_project_ids',
'disk',
'id',
'name',
@@ -568,6 +631,7 @@ class TestFlavorShow(TestFlavor):
data = (
flavor.disabled,
flavor.ephemeral,
+ None,
flavor.disk,
flavor.id,
flavor.name,
@@ -585,6 +649,7 @@ class TestFlavorShow(TestFlavor):
# Return value of _find_resource()
self.flavors_mock.find.return_value = self.flavor
self.flavors_mock.get.side_effect = exceptions.NotFound(None)
+ self.flavor_access_mock.list.return_value = [self.flavor_access]
self.cmd = flavor.ShowFlavor(self.app, None)
def test_show_no_options(self):
@@ -595,7 +660,7 @@ class TestFlavorShow(TestFlavor):
self.assertRaises(tests_utils.ParserException, self.check_parser,
self.cmd, arglist, verifylist)
- def test_flavor_show(self):
+ def test_public_flavor_show(self):
arglist = [
self.flavor.name,
]
@@ -610,12 +675,52 @@ class TestFlavorShow(TestFlavor):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_private_flavor_show(self):
+ private_flavor = compute_fakes.FakeFlavor.create_one_flavor(
+ attrs={
+ 'os-flavor-access:is_public': False,
+ }
+ )
+ self.flavors_mock.find.return_value = private_flavor
+
+ arglist = [
+ private_flavor.name,
+ ]
+ verifylist = [
+ ('flavor', private_flavor.name),
+ ]
+
+ data_with_project = (
+ private_flavor.disabled,
+ private_flavor.ephemeral,
+ self.flavor_access.tenant_id,
+ private_flavor.disk,
+ private_flavor.id,
+ private_flavor.name,
+ private_flavor.is_public,
+ utils.format_dict(private_flavor.get_keys()),
+ private_flavor.ram,
+ private_flavor.rxtx_factor,
+ private_flavor.swap,
+ private_flavor.vcpus,
+ )
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.flavor_access_mock.list.assert_called_with(
+ flavor=private_flavor.id)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(data_with_project, data)
+
class TestFlavorUnset(TestFlavor):
# Return value of self.flavors_mock.find().
flavor = compute_fakes.FakeFlavor.create_one_flavor(
attrs={'os-flavor-access:is_public': False})
+ project = identity_fakes.FakeProject.create_one_project()
def setUp(self):
super(TestFlavorUnset, self).setUp()
@@ -623,11 +728,7 @@ class TestFlavorUnset(TestFlavor):
self.flavors_mock.find.return_value = self.flavor
self.flavors_mock.get.side_effect = exceptions.NotFound(None)
# Return a project
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
self.cmd = flavor.UnsetFlavor(self.app, None)
def test_flavor_unset_property(self):
@@ -644,15 +745,17 @@ class TestFlavorUnset(TestFlavor):
result = self.cmd.take_action(parsed_args)
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
is_public=None)
+ self.flavor.unset_keys.assert_called_with(['property'])
+ self.flavor_access_mock.remove_tenant_access.assert_not_called()
self.assertIsNone(result)
def test_flavor_unset_project(self):
arglist = [
- '--project', identity_fakes.project_id,
+ '--project', self.project.id,
self.flavor.id,
]
verifylist = [
- ('project', identity_fakes.project_id),
+ ('project', self.project.id),
('flavor', self.flavor.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -660,55 +763,51 @@ class TestFlavorUnset(TestFlavor):
result = self.cmd.take_action(parsed_args)
self.assertIsNone(result)
+ self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
+ is_public=None)
self.flavor_access_mock.remove_tenant_access.assert_called_with(
self.flavor.id,
- identity_fakes.project_id,
+ self.project.id,
)
+ self.flavor.unset_keys.assert_not_called()
+ self.assertIsNone(result)
def test_flavor_unset_no_project(self):
arglist = [
- '--project', '',
+ '--project',
self.flavor.id,
]
verifylist = [
- ('project', ''),
+ ('project', None),
('flavor', self.flavor.id),
]
-
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.assertRaises(exceptions.CommandError, self.cmd.take_action,
- parsed_args)
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
def test_flavor_unset_no_flavor(self):
arglist = [
- '--project', identity_fakes.project_id,
+ '--project', self.project.id,
]
verifylist = [
- ('project', identity_fakes.project_id),
+ ('project', self.project.id),
]
-
- self.assertRaises(tests_utils.ParserException,
- self.check_parser,
- self.cmd,
- arglist,
- verifylist)
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
def test_flavor_unset_with_unexist_flavor(self):
self.flavors_mock.get.side_effect = exceptions.NotFound(None)
self.flavors_mock.find.side_effect = exceptions.NotFound(None)
arglist = [
- '--project', identity_fakes.project_id,
+ '--project', self.project.id,
'unexist_flavor',
]
verifylist = [
- ('project', identity_fakes.project_id),
+ ('project', self.project.id),
('flavor', 'unexist_flavor'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- self.assertRaises(exceptions.CommandError,
- self.cmd.take_action,
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args)
def test_flavor_unset_nothing(self):
@@ -718,7 +817,9 @@ class TestFlavorUnset(TestFlavor):
verifylist = [
('flavor', self.flavor.id),
]
-
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.assertRaises(exceptions.CommandError, self.cmd.take_action,
- parsed_args)
+
+ result = self.cmd.take_action(parsed_args)
+ self.assertIsNone(result)
+
+ self.flavor_access_mock.remove_tenant_access.assert_not_called()
diff --git a/openstackclient/tests/compute/v2/test_host.py b/openstackclient/tests/compute/v2/test_host.py
index de53757..9ebed68 100644
--- a/openstackclient/tests/compute/v2/test_host.py
+++ b/openstackclient/tests/compute/v2/test_host.py
@@ -15,6 +15,7 @@
from openstackclient.compute.v2 import host
from openstackclient.tests.compute.v2 import fakes as compute_fakes
+from openstackclient.tests import utils as tests_utils
class TestHost(compute_fakes.TestComputev2):
@@ -27,6 +28,58 @@ class TestHost(compute_fakes.TestComputev2):
self.host_mock.reset_mock()
+class TestHostList(TestHost):
+
+ host = compute_fakes.FakeHost.create_one_host()
+
+ columns = (
+ 'Host Name',
+ 'Service',
+ 'Zone',
+ )
+
+ data = [(
+ host.host_name,
+ host.service,
+ host.zone,
+ )]
+
+ def setUp(self):
+ super(TestHostList, self).setUp()
+
+ self.host_mock.list_all.return_value = [self.host]
+
+ self.cmd = host.ListHost(self.app, None)
+
+ def test_host_list_no_option(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.host_mock.list_all.assert_called_with(None)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_host_list_with_option(self):
+ arglist = [
+ '--zone', self.host.zone,
+ ]
+ verifylist = [
+ ('zone', self.host.zone),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.host_mock.list_all.assert_called_with(self.host.zone)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
class TestHostSet(TestHost):
def setUp(self):
@@ -40,10 +93,10 @@ class TestHostSet(TestHost):
def test_host_set_no_option(self):
arglist = [
- str(self.host.id)
+ self.host.host
]
verifylist = [
- ('host', str(self.host.id))
+ ('host', self.host.host)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -52,18 +105,18 @@ class TestHostSet(TestHost):
self.assertIsNone(result)
body = {}
- self.host_mock.update.assert_called_with(self.host.id, body)
+ self.host_mock.update.assert_called_with(self.host.host, body)
def test_host_set(self):
arglist = [
'--enable',
'--disable-maintenance',
- str(self.host.id)
+ self.host.host
]
verifylist = [
('enable', True),
('enable_maintenance', False),
- ('host', str(self.host.id))
+ ('host', self.host.host)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -71,5 +124,56 @@ class TestHostSet(TestHost):
result = self.cmd.take_action(parsed_args)
self.assertIsNone(result)
- body = {'status': True, 'maintenance_mode': False}
- self.host_mock.update.assert_called_with(self.host.id, body)
+ body = {'status': 'enable', 'maintenance_mode': 'disable'}
+ self.host_mock.update.assert_called_with(self.host.host, body)
+
+
+class TestHostShow(TestHost):
+
+ host = compute_fakes.FakeHost.create_one_host()
+
+ columns = (
+ 'Host',
+ 'Project',
+ 'CPU',
+ 'Memory MB',
+ 'Disk GB',
+ )
+ data = [(
+ host.host,
+ host.project,
+ host.cpu,
+ host.memory_mb,
+ host.disk_gb,
+ )]
+
+ def setUp(self):
+ super(TestHostShow, self).setUp()
+
+ self.host_mock.get.return_value = [self.host]
+
+ self.cmd = host.ShowHost(self.app, None)
+
+ def test_host_show_no_option(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_host_show_with_option(self):
+ arglist = [
+ self.host.host_name,
+ ]
+ verifylist = [
+ ('host', self.host.host_name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.host_mock.get.assert_called_with(self.host.host_name)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
diff --git a/openstackclient/tests/compute/v2/test_hypervisor.py b/openstackclient/tests/compute/v2/test_hypervisor.py
index 8d717ba..ee0f40e 100644
--- a/openstackclient/tests/compute/v2/test_hypervisor.py
+++ b/openstackclient/tests/compute/v2/test_hypervisor.py
@@ -15,7 +15,8 @@
import copy
-from openstackclient.common import exceptions
+from osc_lib import exceptions
+
from openstackclient.compute.v2 import hypervisor
from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests import fakes
diff --git a/openstackclient/tests/compute/v2/test_keypair.py b/openstackclient/tests/compute/v2/test_keypair.py
index a50a532..25949e3 100644
--- a/openstackclient/tests/compute/v2/test_keypair.py
+++ b/openstackclient/tests/compute/v2/test_keypair.py
@@ -14,6 +14,10 @@
#
import mock
+from mock import call
+
+from osc_lib import exceptions
+from osc_lib import utils
from openstackclient.compute.v2 import keypair
from openstackclient.tests.compute.v2 import fakes as compute_fakes
@@ -114,22 +118,23 @@ class TestKeypairCreate(TestKeypair):
class TestKeypairDelete(TestKeypair):
- keypair = compute_fakes.FakeKeypair.create_one_keypair()
+ keypairs = compute_fakes.FakeKeypair.create_keypairs(count=2)
def setUp(self):
super(TestKeypairDelete, self).setUp()
- self.keypairs_mock.get.return_value = self.keypair
+ self.keypairs_mock.get = compute_fakes.FakeKeypair.get_keypairs(
+ self.keypairs)
self.keypairs_mock.delete.return_value = None
self.cmd = keypair.DeleteKeypair(self.app, None)
def test_keypair_delete(self):
arglist = [
- self.keypair.name
+ self.keypairs[0].name
]
verifylist = [
- ('name', self.keypair.name),
+ ('name', [self.keypairs[0].name]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -137,7 +142,54 @@ class TestKeypairDelete(TestKeypair):
ret = self.cmd.take_action(parsed_args)
self.assertIsNone(ret)
- self.keypairs_mock.delete.assert_called_with(self.keypair.name)
+ self.keypairs_mock.delete.assert_called_with(self.keypairs[0].name)
+
+ def test_delete_multiple_keypairs(self):
+ arglist = []
+ for k in self.keypairs:
+ arglist.append(k.name)
+ verifylist = [
+ ('name', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for k in self.keypairs:
+ calls.append(call(k.name))
+ self.keypairs_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_delete_multiple_keypairs_with_exception(self):
+ arglist = [
+ self.keypairs[0].name,
+ 'unexist_keypair',
+ ]
+ verifylist = [
+ ('name', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self.keypairs[0], exceptions.CommandError]
+ with mock.patch.object(utils, 'find_resource',
+ side_effect=find_mock_result) as find_mock:
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 public keys failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(
+ self.keypairs_mock, self.keypairs[0].name)
+ find_mock.assert_any_call(self.keypairs_mock, 'unexist_keypair')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.keypairs_mock.delete.assert_called_once_with(
+ self.keypairs[0].name
+ )
class TestKeypairList(TestKeypair):
diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py
index d642e6d..a98398e 100644
--- a/openstackclient/tests/compute/v2/test_server.py
+++ b/openstackclient/tests/compute/v2/test_server.py
@@ -12,12 +12,14 @@
# License for the specific language governing permissions and limitations
# under the License.
#
+import collections
import getpass
import mock
-
from mock import call
-from openstackclient.common import exceptions
-from openstackclient.common import utils as common_utils
+
+from osc_lib import exceptions
+from osc_lib import utils as common_utils
+
from openstackclient.compute.v2 import server
from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
@@ -42,6 +44,11 @@ class TestServer(compute_fakes.TestComputev2):
self.flavors_mock = self.app.client_manager.compute.flavors
self.flavors_mock.reset_mock()
+ # Get a shortcut to the compute client SecurityGroupManager Mock
+ self.security_groups_mock = \
+ self.app.client_manager.compute.security_groups
+ self.security_groups_mock.reset_mock()
+
# Get a shortcut to the image client ImageManager Mock
self.images_mock = self.app.client_manager.image.images
self.images_mock.reset_mock()
@@ -50,10 +57,10 @@ class TestServer(compute_fakes.TestComputev2):
self.volumes_mock = self.app.client_manager.volume.volumes
self.volumes_mock.reset_mock()
- # Set object attributes to be tested. Could be overwriten in subclass.
+ # Set object attributes to be tested. Could be overwritten in subclass.
self.attrs = {}
- # Set object methods to be tested. Could be overwriten in subclass.
+ # Set object methods to be tested. Could be overwritten in subclass.
self.methods = {}
def setup_servers_mock(self, count):
@@ -87,6 +94,128 @@ class TestServer(compute_fakes.TestComputev2):
self.assertIsNone(result)
+class TestServerAddFixedIP(TestServer):
+
+ def setUp(self):
+ super(TestServerAddFixedIP, self).setUp()
+
+ # Get a shortcut to the compute client ServerManager Mock
+ self.networks_mock = self.app.client_manager.compute.networks
+
+ # Get the command object to test
+ self.cmd = server.AddFixedIP(self.app, None)
+
+ # Set add_fixed_ip method to be tested.
+ self.methods = {
+ 'add_fixed_ip': None,
+ }
+
+ def test_server_add_fixed_ip(self):
+ servers = self.setup_servers_mock(count=1)
+ network = compute_fakes.FakeNetwork.create_one_network()
+ self.networks_mock.get.return_value = network
+
+ arglist = [
+ servers[0].id,
+ network.id,
+ ]
+ verifylist = [
+ ('server', servers[0].id),
+ ('network', network.id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ servers[0].add_fixed_ip.assert_called_once_with(
+ network.id,
+ )
+ self.assertIsNone(result)
+
+
+class TestServerAddFloatingIP(TestServer):
+
+ def setUp(self):
+ super(TestServerAddFloatingIP, self).setUp()
+
+ # Get a shortcut to the compute client ServerManager Mock
+ self.networks_mock = self.app.client_manager.compute.networks
+
+ # Get the command object to test
+ self.cmd = server.AddFloatingIP(self.app, None)
+
+ # Set add_floating_ip method to be tested.
+ self.methods = {
+ 'add_floating_ip': None,
+ }
+
+ def test_server_add_floating_ip(self):
+ servers = self.setup_servers_mock(count=1)
+
+ arglist = [
+ servers[0].id,
+ '1.2.3.4',
+ ]
+ verifylist = [
+ ('server', servers[0].id),
+ ('ip_address', '1.2.3.4'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ servers[0].add_floating_ip.assert_called_once_with('1.2.3.4')
+ self.assertIsNone(result)
+
+
+class TestServerAddSecurityGroup(TestServer):
+
+ def setUp(self):
+ super(TestServerAddSecurityGroup, self).setUp()
+
+ self.security_group = \
+ compute_fakes.FakeSecurityGroup.create_one_security_group()
+ # This is the return value for utils.find_resource() for security group
+ self.security_groups_mock.get.return_value = self.security_group
+
+ attrs = {
+ 'security_groups': [{'name': self.security_group.id}]
+ }
+ methods = {
+ 'add_security_group': None,
+ }
+
+ self.server = compute_fakes.FakeServer.create_one_server(
+ attrs=attrs,
+ methods=methods
+ )
+ # This is the return value for utils.find_resource() for server
+ self.servers_mock.get.return_value = self.server
+
+ # Get the command object to test
+ self.cmd = server.AddServerSecurityGroup(self.app, None)
+
+ def test_server_add_security_group(self):
+ arglist = [
+ self.server.id,
+ self.security_group.id
+ ]
+ verifylist = [
+ ('server', self.server.id),
+ ('group', self.security_group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ self.security_groups_mock.get.assert_called_with(
+ self.security_group.id,
+ )
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.server.add_security_group.assert_called_with(
+ self.security_group.id,
+ )
+ self.assertIsNone(result)
+
+
class TestServerCreate(TestServer):
columns = (
@@ -510,150 +639,6 @@ class TestServerDumpCreate(TestServer):
self.run_method_with_servers('trigger_crash_dump', 3)
-class TestServerImageCreate(TestServer):
-
- columns = (
- 'id',
- 'name',
- 'owner',
- 'protected',
- 'tags',
- 'visibility',
- )
-
- def datalist(self):
- datalist = (
- self.image.id,
- self.image.name,
- self.image.owner,
- self.image.protected,
- self.image.tags,
- self.image.visibility,
- )
- return datalist
-
- def setUp(self):
- super(TestServerImageCreate, self).setUp()
-
- self.server = compute_fakes.FakeServer.create_one_server()
-
- # This is the return value for utils.find_resource()
- self.servers_mock.get.return_value = self.server
-
- self.image = image_fakes.FakeImage.create_one_image()
- self.images_mock.get.return_value = self.image
- self.servers_mock.create_image.return_value = self.image.id
-
- # Get the command object to test
- self.cmd = server.CreateServerImage(self.app, None)
-
- def test_server_image_create_no_options(self):
- arglist = [
- self.server.id,
- ]
- verifylist = [
- ('server', self.server.id),
- ]
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- # In base command class ShowOne in cliff, abstract method take_action()
- # returns a two-part tuple with a tuple of column names and a tuple of
- # data to be shown.
- columns, data = self.cmd.take_action(parsed_args)
-
- # ServerManager.create_image(server, image_name, metadata=)
- self.servers_mock.create_image.assert_called_with(
- self.servers_mock.get.return_value,
- self.server.name,
- )
-
- self.assertEqual(self.columns, columns)
- self.assertEqual(self.datalist(), data)
-
- def test_server_image_create_name(self):
- arglist = [
- '--name', 'img-nam',
- self.server.id,
- ]
- verifylist = [
- ('name', 'img-nam'),
- ('server', self.server.id),
- ]
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- # In base command class ShowOne in cliff, abstract method take_action()
- # returns a two-part tuple with a tuple of column names and a tuple of
- # data to be shown.
- columns, data = self.cmd.take_action(parsed_args)
-
- # ServerManager.create_image(server, image_name, metadata=)
- self.servers_mock.create_image.assert_called_with(
- self.servers_mock.get.return_value,
- 'img-nam',
- )
-
- self.assertEqual(self.columns, columns)
- self.assertEqual(self.datalist(), data)
-
- @mock.patch.object(common_utils, 'wait_for_status', return_value=False)
- def test_server_create_image_with_wait_fails(self, mock_wait_for_status):
- arglist = [
- '--wait',
- self.server.id,
- ]
- verifylist = [
- ('wait', True),
- ('server', self.server.id),
- ]
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- self.assertRaises(SystemExit, self.cmd.take_action, parsed_args)
-
- mock_wait_for_status.assert_called_once_with(
- self.images_mock.get,
- self.image.id,
- callback=server._show_progress
- )
-
- # ServerManager.create_image(server, image_name, metadata=)
- self.servers_mock.create_image.assert_called_with(
- self.servers_mock.get.return_value,
- self.server.name,
- )
-
- @mock.patch.object(common_utils, 'wait_for_status', return_value=True)
- def test_server_create_image_with_wait_ok(self, mock_wait_for_status):
- arglist = [
- '--wait',
- self.server.id,
- ]
- verifylist = [
- ('wait', True),
- ('server', self.server.id),
- ]
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- # In base command class ShowOne in cliff, abstract method take_action()
- # returns a two-part tuple with a tuple of column names and a tuple of
- # data to be shown.
- columns, data = self.cmd.take_action(parsed_args)
-
- # ServerManager.create_image(server, image_name, metadata=)
- self.servers_mock.create_image.assert_called_with(
- self.servers_mock.get.return_value,
- self.server.name,
- )
-
- mock_wait_for_status.assert_called_once_with(
- self.images_mock.get,
- self.image.id,
- callback=server._show_progress
- )
-
- self.assertEqual(self.columns, columns)
- self.assertEqual(self.datalist(), data)
-
-
class TestServerList(TestServer):
# Columns to be listed up.
@@ -662,6 +647,7 @@ class TestServerList(TestServer):
'Name',
'Status',
'Networks',
+ 'Image Name',
)
columns_long = (
'ID',
@@ -670,6 +656,8 @@ class TestServerList(TestServer):
'Task State',
'Power State',
'Networks',
+ 'Image Name',
+ 'Image ID',
'Availability Zone',
'Host',
'Properties',
@@ -732,12 +720,19 @@ class TestServerList(TestServer):
self.data = []
self.data_long = []
+ Image = collections.namedtuple('Image', 'id name')
+ self.images_mock.list.return_value = [
+ Image(id=s.image['id'], name=self.image.name)
+ for s in self.servers
+ ]
+
for s in self.servers:
self.data.append((
s.id,
s.name,
s.status,
server._format_servers_list_networks(s.networks),
+ self.image.name,
))
self.data_long.append((
s.id,
@@ -748,6 +743,8 @@ class TestServerList(TestServer):
getattr(s, 'OS-EXT-STS:power_state')
),
server._format_servers_list_networks(s.networks),
+ self.image.name,
+ s.image['id'],
getattr(s, 'OS-EXT-AZ:availability_zone'),
getattr(s, 'OS-EXT-SRV-ATTR:host'),
s.Metadata,
@@ -795,7 +792,7 @@ class TestServerList(TestServer):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.cimages_mock.get.assert_called_with(self.image.id)
+ self.cimages_mock.get.assert_any_call(self.image.id)
self.search_opts['image'] = self.image.id
self.servers_mock.list.assert_called_with(**self.kwargs)
@@ -986,6 +983,118 @@ class TestServerRebuild(TestServer):
self.server.rebuild.assert_called_with(self.image, None)
+class TestServerRemoveFixedIP(TestServer):
+
+ def setUp(self):
+ super(TestServerRemoveFixedIP, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server.RemoveFixedIP(self.app, None)
+
+ # Set unshelve method to be tested.
+ self.methods = {
+ 'remove_fixed_ip': None,
+ }
+
+ def test_server_remove_fixed_ip(self):
+ servers = self.setup_servers_mock(count=1)
+
+ arglist = [
+ servers[0].id,
+ '1.2.3.4',
+ ]
+ verifylist = [
+ ('server', servers[0].id),
+ ('ip_address', '1.2.3.4'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ servers[0].remove_fixed_ip.assert_called_once_with('1.2.3.4')
+ self.assertIsNone(result)
+
+
+class TestServerRemoveFloatingIP(TestServer):
+
+ def setUp(self):
+ super(TestServerRemoveFloatingIP, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server.RemoveFloatingIP(self.app, None)
+
+ # Set unshelve method to be tested.
+ self.methods = {
+ 'remove_floating_ip': None,
+ }
+
+ def test_server_remove_floating_ip(self):
+ servers = self.setup_servers_mock(count=1)
+
+ arglist = [
+ servers[0].id,
+ '1.2.3.4',
+ ]
+ verifylist = [
+ ('server', servers[0].id),
+ ('ip_address', '1.2.3.4'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ servers[0].remove_floating_ip.assert_called_once_with('1.2.3.4')
+ self.assertIsNone(result)
+
+
+class TestServerRemoveSecurityGroup(TestServer):
+
+ def setUp(self):
+ super(TestServerRemoveSecurityGroup, self).setUp()
+
+ self.security_group = \
+ compute_fakes.FakeSecurityGroup.create_one_security_group()
+ # This is the return value for utils.find_resource() for security group
+ self.security_groups_mock.get.return_value = self.security_group
+
+ attrs = {
+ 'security_groups': [{'name': self.security_group.id}]
+ }
+ methods = {
+ 'remove_security_group': None,
+ }
+
+ self.server = compute_fakes.FakeServer.create_one_server(
+ attrs=attrs,
+ methods=methods
+ )
+ # This is the return value for utils.find_resource() for server
+ self.servers_mock.get.return_value = self.server
+
+ # Get the command object to test
+ self.cmd = server.RemoveServerSecurityGroup(self.app, None)
+
+ def test_server_remove_security_group(self):
+ arglist = [
+ self.server.id,
+ self.security_group.id
+ ]
+ verifylist = [
+ ('server', self.server.id),
+ ('group', self.security_group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ self.security_groups_mock.get.assert_called_with(
+ self.security_group.id,
+ )
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.server.remove_security_group.assert_called_with(
+ self.security_group.id,
+ )
+ self.assertIsNone(result)
+
+
class TestServerResize(TestServer):
def setUp(self):
@@ -1691,7 +1800,7 @@ class TestServerGeneral(TestServer):
(data_1, data_2, networks_format))
self.assertIn(networks_format, (data_1, data_2), msg)
- @mock.patch('openstackclient.common.utils.find_resource')
+ @mock.patch('osc_lib.utils.find_resource')
def test_prep_server_detail(self, find_resource):
# Setup mock method return value. utils.find_resource() will be called
# three times in _prep_server_detail():
@@ -1732,4 +1841,4 @@ class TestServerGeneral(TestServer):
server_detail.pop('networks')
# Check the results.
- self.assertDictEqual(info, server_detail)
+ self.assertEqual(info, server_detail)
diff --git a/openstackclient/tests/compute/v2/test_server_backup.py b/openstackclient/tests/compute/v2/test_server_backup.py
index b35f9f5..8eeb0dc 100644
--- a/openstackclient/tests/compute/v2/test_server_backup.py
+++ b/openstackclient/tests/compute/v2/test_server_backup.py
@@ -13,8 +13,9 @@
import mock
-from openstackclient.common import exceptions
-from openstackclient.common import utils as common_utils
+from osc_lib import exceptions
+from osc_lib import utils as common_utils
+
from openstackclient.compute.v2 import server_backup
from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
@@ -33,10 +34,10 @@ class TestServerBackup(compute_fakes.TestComputev2):
self.images_mock = self.app.client_manager.image.images
self.images_mock.reset_mock()
- # Set object attributes to be tested. Could be overwriten in subclass.
+ # Set object attributes to be tested. Could be overwritten in subclass.
self.attrs = {}
- # Set object methods to be tested. Could be overwriten in subclass.
+ # Set object methods to be tested. Could be overwritten in subclass.
self.methods = {}
def setup_servers_mock(self, count):
diff --git a/openstackclient/tests/compute/v2/test_server_group.py b/openstackclient/tests/compute/v2/test_server_group.py
index 70ff23f..bd5f847 100644
--- a/openstackclient/tests/compute/v2/test_server_group.py
+++ b/openstackclient/tests/compute/v2/test_server_group.py
@@ -15,8 +15,9 @@
import mock
-from openstackclient.common import exceptions
-from openstackclient.common import utils
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.compute.v2 import server_group
from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests import utils as tests_utils
diff --git a/openstackclient/tests/compute/v2/test_server_image.py b/openstackclient/tests/compute/v2/test_server_image.py
new file mode 100644
index 0000000..c3c52da
--- /dev/null
+++ b/openstackclient/tests/compute/v2/test_server_image.py
@@ -0,0 +1,228 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+import mock
+
+from osc_lib import exceptions
+from osc_lib import utils as common_utils
+
+from openstackclient.compute.v2 import server_image
+from openstackclient.tests.compute.v2 import fakes as compute_fakes
+from openstackclient.tests.image.v2 import fakes as image_fakes
+
+
+class TestServerImage(compute_fakes.TestComputev2):
+
+ def setUp(self):
+ super(TestServerImage, self).setUp()
+
+ # Get a shortcut to the compute client ServerManager Mock
+ self.servers_mock = self.app.client_manager.compute.servers
+ self.servers_mock.reset_mock()
+
+ # Get a shortcut to the image client ImageManager Mock
+ self.images_mock = self.app.client_manager.image.images
+ self.images_mock.reset_mock()
+
+ # Set object attributes to be tested. Could be overwritten in subclass.
+ self.attrs = {}
+
+ # Set object methods to be tested. Could be overwritten in subclass.
+ self.methods = {}
+
+ def setup_servers_mock(self, count):
+ servers = compute_fakes.FakeServer.create_servers(
+ attrs=self.attrs,
+ methods=self.methods,
+ count=count,
+ )
+
+ # This is the return value for utils.find_resource()
+ self.servers_mock.get = compute_fakes.FakeServer.get_servers(
+ servers,
+ 0,
+ )
+ return servers
+
+
+class TestServerImageCreate(TestServerImage):
+
+ def image_columns(self, image):
+ columnlist = tuple(sorted(image.keys()))
+ return columnlist
+
+ def image_data(self, image):
+ datalist = (
+ image['id'],
+ image['name'],
+ image['owner'],
+ image['protected'],
+ 'active',
+ common_utils.format_list(image.get('tags')),
+ image['visibility'],
+ )
+ return datalist
+
+ def setUp(self):
+ super(TestServerImageCreate, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server_image.CreateServerImage(self.app, None)
+
+ self.methods = {
+ 'create_image': None,
+ }
+
+ def setup_images_mock(self, count, servers=None):
+ if servers:
+ images = image_fakes.FakeImage.create_images(
+ attrs={
+ 'name': servers[0].name,
+ 'status': 'active',
+ },
+ count=count,
+ )
+ else:
+ images = image_fakes.FakeImage.create_images(
+ attrs={
+ 'status': 'active',
+ },
+ count=count,
+ )
+
+ self.images_mock.get = mock.MagicMock(side_effect=images)
+ self.servers_mock.create_image = mock.MagicMock(
+ return_value=images[0].id,
+ )
+ return images
+
+ def test_server_image_create_defaults(self):
+ servers = self.setup_servers_mock(count=1)
+ images = self.setup_images_mock(count=1, servers=servers)
+
+ arglist = [
+ servers[0].id,
+ ]
+ verifylist = [
+ ('server', servers[0].id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class ShowOne in cliff, abstract method take_action()
+ # returns a two-part tuple with a tuple of column names and a tuple of
+ # data to be shown.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # ServerManager.create_image(server, image_name, metadata=)
+ self.servers_mock.create_image.assert_called_with(
+ servers[0].id,
+ servers[0].name,
+ )
+
+ self.assertEqual(self.image_columns(images[0]), columns)
+ self.assertEqual(self.image_data(images[0]), data)
+
+ def test_server_image_create_options(self):
+ servers = self.setup_servers_mock(count=1)
+ images = self.setup_images_mock(count=1, servers=servers)
+
+ arglist = [
+ '--name', 'img-nam',
+ servers[0].id,
+ ]
+ verifylist = [
+ ('name', 'img-nam'),
+ ('server', servers[0].id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class ShowOne in cliff, abstract method take_action()
+ # returns a two-part tuple with a tuple of column names and a tuple of
+ # data to be shown.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # ServerManager.create_image(server, image_name, metadata=)
+ self.servers_mock.create_image.assert_called_with(
+ servers[0].id,
+ 'img-nam',
+ )
+
+ self.assertEqual(self.image_columns(images[0]), columns)
+ self.assertEqual(self.image_data(images[0]), data)
+
+ @mock.patch.object(common_utils, 'wait_for_status', return_value=False)
+ def test_server_create_image_wait_fail(self, mock_wait_for_status):
+ servers = self.setup_servers_mock(count=1)
+ images = self.setup_images_mock(count=1, servers=servers)
+
+ arglist = [
+ '--wait',
+ servers[0].id,
+ ]
+ verifylist = [
+ ('wait', True),
+ ('server', servers[0].id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args,
+ )
+
+ # ServerManager.create_image(server, image_name, metadata=)
+ self.servers_mock.create_image.assert_called_with(
+ servers[0].id,
+ servers[0].name,
+ )
+
+ mock_wait_for_status.assert_called_once_with(
+ self.images_mock.get,
+ images[0].id,
+ callback=mock.ANY
+ )
+
+ @mock.patch.object(common_utils, 'wait_for_status', return_value=True)
+ def test_server_create_image_wait_ok(self, mock_wait_for_status):
+ servers = self.setup_servers_mock(count=1)
+ images = self.setup_images_mock(count=1, servers=servers)
+
+ arglist = [
+ '--wait',
+ servers[0].id,
+ ]
+ verifylist = [
+ ('wait', True),
+ ('server', servers[0].id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class ShowOne in cliff, abstract method take_action()
+ # returns a two-part tuple with a tuple of column names and a tuple of
+ # data to be shown.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # ServerManager.create_image(server, image_name, metadata=)
+ self.servers_mock.create_image.assert_called_with(
+ servers[0].id,
+ servers[0].name,
+ )
+
+ mock_wait_for_status.assert_called_once_with(
+ self.images_mock.get,
+ images[0].id,
+ callback=mock.ANY
+ )
+
+ self.assertEqual(self.image_columns(images[0]), columns)
+ self.assertEqual(self.image_data(images[0]), data)
diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py
index cf53497..1599f46 100644
--- a/openstackclient/tests/compute/v2/test_service.py
+++ b/openstackclient/tests/compute/v2/test_service.py
@@ -13,7 +13,11 @@
# under the License.
#
-from openstackclient.common import exceptions
+import mock
+from mock import call
+
+from osc_lib import exceptions
+
from openstackclient.compute.v2 import service
from openstackclient.tests.compute.v2 import fakes as compute_fakes
@@ -30,40 +34,106 @@ class TestService(compute_fakes.TestComputev2):
class TestServiceDelete(TestService):
+ services = compute_fakes.FakeService.create_services(count=2)
+
def setUp(self):
super(TestServiceDelete, self).setUp()
- self.service = compute_fakes.FakeService.create_one_service()
-
self.service_mock.delete.return_value = None
# Get the command object to test
self.cmd = service.DeleteService(self.app, None)
- def test_service_delete_no_options(self):
+ def test_service_delete(self):
arglist = [
- self.service.binary,
+ self.services[0].binary,
]
verifylist = [
- ('service', self.service.binary),
+ ('service', [self.services[0].binary]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.service_mock.delete.assert_called_with(
- self.service.binary,
+ self.services[0].binary,
)
self.assertIsNone(result)
+ def test_multi_services_delete(self):
+ arglist = []
+ for s in self.services:
+ arglist.append(s.binary)
+ verifylist = [
+ ('service', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for s in self.services:
+ calls.append(call(s.binary))
+ self.service_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_multi_services_delete_with_exception(self):
+ arglist = [
+ self.services[0].binary,
+ 'unexist_service',
+ ]
+ verifylist = [
+ ('service', arglist)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ delete_mock_result = [None, exceptions.CommandError]
+ self.service_mock.delete = (
+ mock.MagicMock(side_effect=delete_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual(
+ '1 of 2 compute services failed to delete.', str(e))
+
+ self.service_mock.delete.assert_any_call(self.services[0].binary)
+ self.service_mock.delete.assert_any_call('unexist_service')
+
class TestServiceList(TestService):
+ service = compute_fakes.FakeService.create_one_service()
+
+ columns = (
+ 'ID',
+ 'Binary',
+ 'Host',
+ 'Zone',
+ 'Status',
+ 'State',
+ 'Updated At',
+ )
+ columns_long = columns + (
+ 'Disabled Reason',
+ )
+
+ data = [(
+ service.id,
+ service.binary,
+ service.host,
+ service.zone,
+ service.status,
+ service.state,
+ service.updated_at,
+ )]
+ data_long = [data[0] + (service.disabled_reason, )]
+
def setUp(self):
super(TestServiceList, self).setUp()
- self.service = compute_fakes.FakeService.create_one_service()
-
self.service_mock.list.return_value = [self.service]
# Get the command object to test
@@ -90,8 +160,8 @@ class TestServiceList(TestService):
self.service.binary,
)
- self.assertNotIn("Disabled Reason", columns)
- self.assertNotIn(self.service.disabled_reason, list(data)[0])
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
def test_service_list_with_long_option(self):
arglist = [
@@ -111,8 +181,13 @@ class TestServiceList(TestService):
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- self.assertIn("Disabled Reason", columns)
- self.assertIn(self.service.disabled_reason, list(data)[0])
+ self.service_mock.list.assert_called_with(
+ self.service.host,
+ self.service.binary,
+ )
+
+ self.assertEqual(self.columns_long, columns)
+ self.assertEqual(self.data_long, list(data))
class TestServiceSet(TestService):
@@ -224,8 +299,12 @@ class TestServiceSet(TestService):
('service', self.service.binary),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.assertRaises(exceptions.CommandError, self.cmd.take_action,
- parsed_args)
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail("CommandError should be raised.")
+ except exceptions.CommandError as e:
+ self.assertEqual("Cannot specify option --disable-reason without "
+ "--disable specified.", str(e))
def test_service_set_enable_with_disable_reason(self):
reason = 'earthquake'
@@ -242,5 +321,90 @@ class TestServiceSet(TestService):
('service', self.service.binary),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.assertRaises(exceptions.CommandError, self.cmd.take_action,
- parsed_args)
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail("CommandError should be raised.")
+ except exceptions.CommandError as e:
+ self.assertEqual("Cannot specify option --disable-reason without "
+ "--disable specified.", str(e))
+
+ def test_service_set_state_up(self):
+ arglist = [
+ '--up',
+ self.service.host,
+ self.service.binary,
+ ]
+ verifylist = [
+ ('up', True),
+ ('host', self.service.host),
+ ('service', self.service.binary),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ self.service_mock.force_down.assert_called_once_with(
+ self.service.host, self.service.binary, force_down=False)
+ self.assertNotCalled(self.service_mock.enable)
+ self.assertNotCalled(self.service_mock.disable)
+ self.assertIsNone(result)
+
+ def test_service_set_state_down(self):
+ arglist = [
+ '--down',
+ self.service.host,
+ self.service.binary,
+ ]
+ verifylist = [
+ ('down', True),
+ ('host', self.service.host),
+ ('service', self.service.binary),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ self.service_mock.force_down.assert_called_once_with(
+ self.service.host, self.service.binary, force_down=True)
+ self.assertNotCalled(self.service_mock.enable)
+ self.assertNotCalled(self.service_mock.disable)
+ self.assertIsNone(result)
+
+ def test_service_set_enable_and_state_down(self):
+ arglist = [
+ '--enable',
+ '--down',
+ self.service.host,
+ self.service.binary,
+ ]
+ verifylist = [
+ ('enable', True),
+ ('down', True),
+ ('host', self.service.host),
+ ('service', self.service.binary),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ self.service_mock.enable.assert_called_once_with(
+ self.service.host, self.service.binary)
+ self.service_mock.force_down.assert_called_once_with(
+ self.service.host, self.service.binary, force_down=True)
+ self.assertIsNone(result)
+
+ def test_service_set_enable_and_state_down_with_exception(self):
+ arglist = [
+ '--enable',
+ '--down',
+ self.service.host,
+ self.service.binary,
+ ]
+ verifylist = [
+ ('enable', True),
+ ('down', True),
+ ('host', self.service.host),
+ ('service', self.service.binary),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ with mock.patch.object(self.service_mock, 'enable',
+ side_effect=Exception()):
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
+ self.service_mock.force_down.assert_called_once_with(
+ self.service.host, self.service.binary, force_down=True)
diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py
index ad4705a..786cd6d 100644
--- a/openstackclient/tests/fakes.py
+++ b/openstackclient/tests/fakes.py
@@ -15,11 +15,11 @@
import json
import mock
-import six
import sys
from keystoneauth1 import fixture
import requests
+import six
AUTH_TOKEN = "foobar"
@@ -50,6 +50,21 @@ TEST_RESPONSE_DICT_V3.set_project_scope()
TEST_VERSIONS = fixture.DiscoveryList(href=AUTH_URL)
+def to_unicode_dict(catalog_dict):
+ """Converts dict to unicode dict
+
+ """
+ if isinstance(catalog_dict, dict):
+ return {to_unicode_dict(key): to_unicode_dict(value)
+ for key, value in catalog_dict.items()}
+ elif isinstance(catalog_dict, list):
+ return [to_unicode_dict(element) for element in catalog_dict]
+ elif isinstance(catalog_dict, str):
+ return catalog_dict + u""
+ else:
+ return catalog_dict
+
+
class FakeStdout(object):
def __init__(self):
diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py
index b37bd9d..a715427 100644
--- a/openstackclient/tests/identity/v2_0/fakes.py
+++ b/openstackclient/tests/identity/v2_0/fakes.py
@@ -13,7 +13,12 @@
# under the License.
#
+import copy
import mock
+import uuid
+
+from keystoneauth1 import access
+from keystoneauth1 import fixture
from openstackclient.tests import fakes
from openstackclient.tests import utils
@@ -45,6 +50,11 @@ ROLE = {
'name': role_name,
}
+ROLE_2 = {
+ 'id': '2',
+ 'name': 'bigboss',
+}
+
service_id = '1925-10-11'
service_name = 'elmore'
service_description = 'Leonard, Elmore, rip'
@@ -71,19 +81,19 @@ USER = {
}
token_expires = '2014-01-01T00:00:00Z'
-token_id = 'tttttttt-tttt-tttt-tttt-tttttttttttt'
+token_id = 'token-id-' + uuid.uuid4().hex
TOKEN = {
'expires': token_expires,
'id': token_id,
- 'tenant_id': project_id,
- 'user_id': user_id,
+ 'tenant_id': 'project-id',
+ 'user_id': 'user-id',
}
UNSCOPED_TOKEN = {
'expires': token_expires,
'id': token_id,
- 'user_id': user_id,
+ 'user_id': 'user-id',
}
endpoint_name = service_name
@@ -106,25 +116,42 @@ ENDPOINT = {
'service_id': endpoint_service_id,
}
-extension_name = 'OpenStack Keystone User CRUD'
-extension_namespace = 'http://docs.openstack.org/identity/'\
- 'api/ext/OS-KSCRUD/v1.0'
-extension_description = 'OpenStack extensions to Keystone v2.0 API'\
- ' enabling User Operations.'
-extension_updated = '2013-07-07T12:00:0-00:00'
-extension_alias = 'OS-KSCRUD'
-extension_links = '[{"href":'\
- '"https://github.com/openstack/identity-api", "type":'\
- ' "text/html", "rel": "describedby"}]'
-
-EXTENSION = {
- 'name': extension_name,
- 'namespace': extension_namespace,
- 'description': extension_description,
- 'updated': extension_updated,
- 'alias': extension_alias,
- 'links': extension_links,
-}
+
+def fake_auth_ref(fake_token, fake_service=None):
+ """Create an auth_ref using keystoneauth's fixtures"""
+ token_copy = copy.deepcopy(fake_token)
+ token_copy['token_id'] = token_copy.pop('id')
+ token = fixture.V2Token(**token_copy)
+ # An auth_ref is actually an access info object
+ auth_ref = access.create(body=token)
+
+ # Create a service catalog
+ if fake_service:
+ service = token.add_service(
+ fake_service.type,
+ fake_service.name,
+ )
+ # TODO(dtroyer): Add an 'id' element to KSA's _Service fixure
+ service['id'] = fake_service.id
+ for e in fake_service.endpoints:
+ # KSA's _Service fixture copies publicURL to internalURL and
+ # adminURL if they do not exist. Soooo helpful...
+ internal = e.get('internalURL', None)
+ admin = e.get('adminURL', None)
+ region = e.get('region_id') or e.get('region', '<none>')
+ endpoint = service.add_endpoint(
+ public=e['publicURL'],
+ internal=internal,
+ admin=admin,
+ region=region,
+ )
+ # ...so undo that helpfulness
+ if not internal:
+ endpoint['internalURL'] = None
+ if not admin:
+ endpoint['adminURL'] = None
+
+ return auth_ref
class FakeIdentityv2Client(object):
@@ -166,3 +193,323 @@ class TestIdentityv2(utils.TestCommand):
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)
+
+
+class FakeExtension(object):
+ """Fake one or more extension."""
+
+ @staticmethod
+ def create_one_extension(attrs=None):
+ """Create a fake extension.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object with name, namespace, etc.
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ extension_info = {
+ 'name': 'name-' + uuid.uuid4().hex,
+ 'namespace': ('http://docs.openstack.org/identity/'
+ 'api/ext/OS-KSCRUD/v1.0'),
+ 'description': 'description-' + uuid.uuid4().hex,
+ 'updated': '2013-07-07T12:00:0-00:00',
+ 'alias': 'OS-KSCRUD',
+ 'links': ('[{"href":'
+ '"https://github.com/openstack/identity-api", "type":'
+ ' "text/html", "rel": "describedby"}]')
+ }
+
+ # Overwrite default attributes.
+ extension_info.update(attrs)
+
+ extension = fakes.FakeResource(
+ info=copy.deepcopy(extension_info),
+ loaded=True)
+ return extension
+
+
+class FakeCatalog(object):
+ """Fake one or more catalog."""
+
+ @staticmethod
+ def create_catalog(attrs=None):
+ """Create a fake catalog.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object with id, name, type and so on.
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ catalog_info = {
+ 'id': 'service-id-' + uuid.uuid4().hex,
+ 'type': 'compute',
+ 'name': 'supernova',
+ 'endpoints': [
+ {
+ 'region': 'one',
+ 'publicURL': 'https://public.one.example.com',
+ 'internalURL': 'https://internal.one.example.com',
+ 'adminURL': 'https://admin.one.example.com',
+ },
+ {
+ 'region': 'two',
+ 'publicURL': 'https://public.two.example.com',
+ 'internalURL': 'https://internal.two.example.com',
+ 'adminURL': 'https://admin.two.example.com',
+ },
+ {
+ 'region': None,
+ 'publicURL': 'https://public.none.example.com',
+ 'internalURL': 'https://internal.none.example.com',
+ 'adminURL': 'https://admin.none.example.com',
+ },
+ ],
+ }
+ # Overwrite default attributes.
+ catalog_info.update(attrs)
+
+ catalog = fakes.FakeResource(
+ info=copy.deepcopy(catalog_info),
+ loaded=True)
+
+ return catalog
+
+
+class FakeProject(object):
+ """Fake one or more project."""
+
+ @staticmethod
+ def create_one_project(attrs=None):
+ """Create a fake project.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, name, and so on
+ """
+
+ attrs = attrs or {}
+
+ # set default attributes.
+ project_info = {
+ 'id': 'project-id-' + uuid.uuid4().hex,
+ 'name': 'project-name-' + uuid.uuid4().hex,
+ 'description': 'project_description',
+ 'enabled': True,
+ }
+ project_info.update(attrs)
+
+ project = fakes.FakeResource(info=copy.deepcopy(project_info),
+ loaded=True)
+ return project
+
+ @staticmethod
+ def create_projects(attrs=None, count=2):
+ """Create multiple fake projects.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of projects to fake
+ :return:
+ A list of FakeResource objects faking the projects
+ """
+ projects = []
+ for i in range(0, count):
+ projects.append(FakeProject.create_one_project(attrs))
+
+ return projects
+
+
+class FakeEndpoint(object):
+ """Fake one or more endpoint."""
+
+ @staticmethod
+ def create_one_endpoint(attrs=None):
+ """Create a fake agent.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, name, region, and so on
+ """
+
+ attrs = attrs or {}
+
+ # set default attributes.
+ endpoint_info = {
+ 'service_name': 'service-name-' + uuid.uuid4().hex,
+ 'adminurl': 'http://endpoint_adminurl',
+ 'region': 'endpoint_region',
+ 'internalurl': 'http://endpoint_internalurl',
+ 'service_type': 'service_type',
+ 'id': 'endpoint-id-' + uuid.uuid4().hex,
+ 'publicurl': 'http://endpoint_publicurl',
+ 'service_id': 'service-name-' + uuid.uuid4().hex,
+
+ }
+ endpoint_info.update(attrs)
+
+ endpoint = fakes.FakeResource(info=copy.deepcopy(endpoint_info),
+ loaded=True)
+ return endpoint
+
+ @staticmethod
+ def create_endpoints(attrs=None, count=2):
+ """Create multiple fake endpoints.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of endpoints to fake
+ :return:
+ A list of FakeResource objects faking the endpoints
+ """
+ endpoints = []
+ for i in range(0, count):
+ endpoints.append(FakeEndpoint.create_one_endpoint(attrs))
+
+ return endpoints
+
+
+class FakeService(object):
+ """Fake one or more service."""
+
+ @staticmethod
+ def create_one_service(attrs=None):
+ """Create a fake service.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, name, type, and so on
+ """
+
+ attrs = attrs or {}
+
+ # set default attributes.
+ service_info = {
+ 'id': 'service-id-' + uuid.uuid4().hex,
+ 'name': 'service-name-' + uuid.uuid4().hex,
+ 'description': 'service_description',
+ 'type': 'service_type',
+
+ }
+ service_info.update(attrs)
+
+ service = fakes.FakeResource(info=copy.deepcopy(service_info),
+ loaded=True)
+ return service
+
+ @staticmethod
+ def create_services(attrs=None, count=2):
+ """Create multiple fake services.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of services to fake
+ :return:
+ A list of FakeResource objects faking the services
+ """
+ services = []
+ for i in range(0, count):
+ services.append(FakeService.create_one_service(attrs))
+
+ return services
+
+
+class FakeRole(object):
+ """Fake one or more role."""
+
+ @staticmethod
+ def create_one_role(attrs=None):
+ """Create a fake role.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, name, and so on
+ """
+
+ attrs = attrs or {}
+
+ # set default attributes.
+ role_info = {
+ 'id': 'role-id' + uuid.uuid4().hex,
+ 'name': 'role-name' + uuid.uuid4().hex,
+ }
+ role_info.update(attrs)
+
+ role = fakes.FakeResource(info=copy.deepcopy(role_info),
+ loaded=True)
+ return role
+
+ @staticmethod
+ def create_roles(attrs=None, count=2):
+ """Create multiple fake roles.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of roles to fake
+ :return:
+ A list of FakeResource objects faking the roles
+ """
+ roles = []
+ for i in range(0, count):
+ roles.append(FakeRole.create_one_role(attrs))
+
+ return roles
+
+
+class FakeUser(object):
+ """Fake one or more user."""
+
+ @staticmethod
+ def create_one_user(attrs=None):
+ """Create a fake user.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, name, and so on
+ """
+ attrs = attrs or {}
+
+ # set default attributes.
+ user_info = {
+ 'id': 'user-id-' + uuid.uuid4().hex,
+ 'name': 'user-name-' + uuid.uuid4().hex,
+ 'tenantId': 'project-id-' + uuid.uuid4().hex,
+ 'email': 'admin@openstack.org',
+ 'enabled': True,
+ }
+ user_info.update(attrs)
+
+ user = fakes.FakeResource(info=copy.deepcopy(user_info),
+ loaded=True)
+ return user
+
+ @staticmethod
+ def create_users(attrs=None, count=2):
+ """Create multiple fake users.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of users to fake
+ :return:
+ A list of FakeResource objects faking the users
+ """
+ users = []
+ for i in range(0, count):
+ users.append(FakeUser.create_one_user(attrs))
+
+ return users
diff --git a/openstackclient/tests/identity/v2_0/test_catalog.py b/openstackclient/tests/identity/v2_0/test_catalog.py
index d9ae6a8..487d8f3 100644
--- a/openstackclient/tests/identity/v2_0/test_catalog.py
+++ b/openstackclient/tests/identity/v2_0/test_catalog.py
@@ -14,43 +14,20 @@
import mock
from openstackclient.identity.v2_0 import catalog
+from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
from openstackclient.tests import utils
class TestCatalog(utils.TestCommand):
- fake_service = {
- 'id': 'qwertyuiop',
- 'type': 'compute',
- 'name': 'supernova',
- 'endpoints': [
- {
- 'region': 'one',
- 'publicURL': 'https://public.one.example.com',
- 'internalURL': 'https://internal.one.example.com',
- 'adminURL': 'https://admin.one.example.com',
- },
- {
- 'region': 'two',
- 'publicURL': 'https://public.two.example.com',
- 'internalURL': 'https://internal.two.example.com',
- 'adminURL': 'https://admin.two.example.com',
- },
- {
- 'region': None,
- 'publicURL': 'https://public.none.example.com',
- 'internalURL': 'https://internal.none.example.com',
- 'adminURL': 'https://admin.none.example.com',
- },
- ],
- }
+ service_catalog = identity_fakes.FakeCatalog.create_catalog()
def setUp(self):
super(TestCatalog, self).setUp()
self.sc_mock = mock.MagicMock()
- self.sc_mock.service_catalog.get_data.return_value = [
- self.fake_service,
+ self.sc_mock.service_catalog.catalog.return_value = [
+ self.service_catalog,
]
self.auth_mock = mock.MagicMock()
@@ -74,6 +51,13 @@ class TestCatalogList(TestCatalog):
self.cmd = catalog.ListCatalog(self.app, None)
def test_catalog_list(self):
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.TOKEN,
+ fake_service=self.service_catalog,
+ )
+ self.ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = self.ar_mock
+
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -82,7 +66,6 @@ class TestCatalogList(TestCatalog):
# returns a tuple containing the column names and an iterable
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- self.sc_mock.service_catalog.get_data.assert_called_with()
self.assertEqual(self.columns, columns)
datalist = ((
@@ -101,7 +84,7 @@ class TestCatalogList(TestCatalog):
self.assertEqual(datalist, tuple(data))
def test_catalog_list_with_endpoint_url(self):
- fake_service = {
+ attr = {
'id': 'qwertyuiop',
'type': 'compute',
'name': 'supernova',
@@ -117,9 +100,13 @@ class TestCatalogList(TestCatalog):
},
],
}
- self.sc_mock.service_catalog.get_data.return_value = [
- fake_service,
- ]
+ service_catalog = identity_fakes.FakeCatalog.create_catalog(attr)
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.TOKEN,
+ fake_service=service_catalog,
+ )
+ self.ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = self.ar_mock
arglist = []
verifylist = []
@@ -129,7 +116,6 @@ class TestCatalogList(TestCatalog):
# returns a tuple containing the column names and an iterable
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- self.sc_mock.service_catalog.get_data.assert_called_with()
self.assertEqual(self.columns, columns)
datalist = ((
@@ -151,6 +137,13 @@ class TestCatalogShow(TestCatalog):
self.cmd = catalog.ShowCatalog(self.app, None)
def test_catalog_show(self):
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.UNSCOPED_TOKEN,
+ fake_service=self.service_catalog,
+ )
+ self.ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = self.ar_mock
+
arglist = [
'compute',
]
@@ -163,7 +156,6 @@ class TestCatalogShow(TestCatalog):
# returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
- self.sc_mock.service_catalog.get_data.assert_called_with()
collist = ('endpoints', 'id', 'name', 'type')
self.assertEqual(collist, columns)
@@ -177,7 +169,7 @@ class TestCatalogShow(TestCatalog):
'<none>\n publicURL: https://public.none.example.com\n '
'internalURL: https://internal.none.example.com\n '
'adminURL: https://admin.none.example.com\n',
- 'qwertyuiop',
+ self.service_catalog.id,
'supernova',
'compute',
)
diff --git a/openstackclient/tests/identity/v2_0/test_endpoint.py b/openstackclient/tests/identity/v2_0/test_endpoint.py
index 45ece45..26ec654 100644
--- a/openstackclient/tests/identity/v2_0/test_endpoint.py
+++ b/openstackclient/tests/identity/v2_0/test_endpoint.py
@@ -11,15 +11,19 @@
# under the License.
#
-import copy
-
from openstackclient.identity.v2_0 import endpoint
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
class TestEndpoint(identity_fakes.TestIdentityv2):
+ fake_service = identity_fakes.FakeService.create_one_service()
+ attr = {
+ 'service_name': fake_service.name,
+ 'service_id': fake_service.id,
+ }
+ fake_endpoint = identity_fakes.FakeEndpoint.create_one_endpoint(attr)
+
def setUp(self):
super(TestEndpoint, self).setUp()
@@ -37,35 +41,27 @@ class TestEndpointCreate(TestEndpoint):
def setUp(self):
super(TestEndpointCreate, self).setUp()
- self.endpoints_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ENDPOINT),
- loaded=True,
- )
+ self.endpoints_mock.create.return_value = self.fake_endpoint
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
+ self.services_mock.get.return_value = self.fake_service
# Get the command object to test
self.cmd = endpoint.CreateEndpoint(self.app, None)
def test_endpoint_create(self):
arglist = [
- '--publicurl', identity_fakes.endpoint_publicurl,
- '--internalurl', identity_fakes.endpoint_internalurl,
- '--adminurl', identity_fakes.endpoint_adminurl,
- '--region', identity_fakes.endpoint_region,
- identity_fakes.endpoint_name,
+ '--publicurl', self.fake_endpoint.publicurl,
+ '--internalurl', self.fake_endpoint.internalurl,
+ '--adminurl', self.fake_endpoint.adminurl,
+ '--region', self.fake_endpoint.region,
+ self.fake_service.id,
]
verifylist = [
- ('adminurl', identity_fakes.endpoint_adminurl),
- ('internalurl', identity_fakes.endpoint_internalurl),
- ('publicurl', identity_fakes.endpoint_publicurl),
- ('region', identity_fakes.endpoint_region),
- ('service', identity_fakes.service_name),
+ ('adminurl', self.fake_endpoint.adminurl),
+ ('internalurl', self.fake_endpoint.internalurl),
+ ('publicurl', self.fake_endpoint.publicurl),
+ ('region', self.fake_endpoint.region),
+ ('service', self.fake_service.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -77,25 +73,25 @@ class TestEndpointCreate(TestEndpoint):
# EndpointManager.create(region, service_id, publicurl, adminurl,
# internalurl)
self.endpoints_mock.create.assert_called_with(
- identity_fakes.endpoint_region,
- identity_fakes.service_id,
- identity_fakes.endpoint_publicurl,
- identity_fakes.endpoint_adminurl,
- identity_fakes.endpoint_internalurl,
+ self.fake_endpoint.region,
+ self.fake_service.id,
+ self.fake_endpoint.publicurl,
+ self.fake_endpoint.adminurl,
+ self.fake_endpoint.internalurl,
)
collist = ('adminurl', 'id', 'internalurl', 'publicurl',
'region', 'service_id', 'service_name', 'service_type')
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.endpoint_adminurl,
- identity_fakes.endpoint_id,
- identity_fakes.endpoint_internalurl,
- identity_fakes.endpoint_publicurl,
- identity_fakes.endpoint_region,
- identity_fakes.service_id,
- identity_fakes.service_name,
- identity_fakes.service_type,
+ self.fake_endpoint.adminurl,
+ self.fake_endpoint.id,
+ self.fake_endpoint.internalurl,
+ self.fake_endpoint.publicurl,
+ self.fake_endpoint.region,
+ self.fake_endpoint.service_id,
+ self.fake_endpoint.service_name,
+ self.fake_endpoint.service_type,
)
self.assertEqual(datalist, data)
@@ -106,18 +102,7 @@ class TestEndpointDelete(TestEndpoint):
def setUp(self):
super(TestEndpointDelete, self).setUp()
- self.endpoints_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ENDPOINT),
- loaded=True,
- )
-
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
-
+ self.endpoints_mock.get.return_value = self.fake_endpoint
self.endpoints_mock.delete.return_value = None
# Get the command object to test
@@ -125,17 +110,17 @@ class TestEndpointDelete(TestEndpoint):
def test_endpoint_delete_no_options(self):
arglist = [
- identity_fakes.endpoint_id,
+ self.fake_endpoint.id,
]
verifylist = [
- ('endpoint', identity_fakes.endpoint_id),
+ ('endpoints', [self.fake_endpoint.id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.endpoints_mock.delete.assert_called_with(
- identity_fakes.endpoint_id,
+ self.fake_endpoint.id,
)
self.assertIsNone(result)
@@ -145,19 +130,9 @@ class TestEndpointList(TestEndpoint):
def setUp(self):
super(TestEndpointList, self).setUp()
- self.endpoints_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ENDPOINT),
- loaded=True,
- ),
- ]
+ self.endpoints_mock.list.return_value = [self.fake_endpoint]
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
+ self.services_mock.get.return_value = self.fake_service
# Get the command object to test
self.cmd = endpoint.ListEndpoint(self.app, None)
@@ -177,10 +152,10 @@ class TestEndpointList(TestEndpoint):
collist = ('ID', 'Region', 'Service Name', 'Service Type')
self.assertEqual(collist, columns)
datalist = ((
- identity_fakes.endpoint_id,
- identity_fakes.endpoint_region,
- identity_fakes.service_name,
- identity_fakes.service_type,
+ self.fake_endpoint.id,
+ self.fake_endpoint.region,
+ self.fake_endpoint.service_name,
+ self.fake_endpoint.service_type,
), )
self.assertEqual(datalist, tuple(data))
@@ -204,13 +179,13 @@ class TestEndpointList(TestEndpoint):
'PublicURL', 'AdminURL', 'InternalURL')
self.assertEqual(collist, columns)
datalist = ((
- identity_fakes.endpoint_id,
- identity_fakes.endpoint_region,
- identity_fakes.service_name,
- identity_fakes.service_type,
- identity_fakes.endpoint_publicurl,
- identity_fakes.endpoint_adminurl,
- identity_fakes.endpoint_internalurl,
+ self.fake_endpoint.id,
+ self.fake_endpoint.region,
+ self.fake_endpoint.service_name,
+ self.fake_endpoint.service_type,
+ self.fake_endpoint.publicurl,
+ self.fake_endpoint.adminurl,
+ self.fake_endpoint.internalurl,
), )
self.assertEqual(datalist, tuple(data))
@@ -220,29 +195,19 @@ class TestEndpointShow(TestEndpoint):
def setUp(self):
super(TestEndpointShow, self).setUp()
- self.endpoints_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ENDPOINT),
- loaded=True,
- ),
- ]
+ self.endpoints_mock.list.return_value = [self.fake_endpoint]
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
+ self.services_mock.get.return_value = self.fake_service
# Get the command object to test
self.cmd = endpoint.ShowEndpoint(self.app, None)
def test_endpoint_show(self):
arglist = [
- identity_fakes.endpoint_name,
+ self.fake_endpoint.id,
]
verifylist = [
- ('endpoint_or_service', identity_fakes.endpoint_name),
+ ('endpoint_or_service', self.fake_endpoint.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -255,20 +220,20 @@ class TestEndpointShow(TestEndpoint):
self.endpoints_mock.list.assert_called_with()
# ServiceManager.get(name)
self.services_mock.get.assert_called_with(
- identity_fakes.service_name,
+ self.fake_endpoint.service_id,
)
collist = ('adminurl', 'id', 'internalurl', 'publicurl',
'region', 'service_id', 'service_name', 'service_type')
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.endpoint_adminurl,
- identity_fakes.endpoint_id,
- identity_fakes.endpoint_internalurl,
- identity_fakes.endpoint_publicurl,
- identity_fakes.endpoint_region,
- identity_fakes.service_id,
- identity_fakes.service_name,
- identity_fakes.service_type,
+ self.fake_endpoint.adminurl,
+ self.fake_endpoint.id,
+ self.fake_endpoint.internalurl,
+ self.fake_endpoint.publicurl,
+ self.fake_endpoint.region,
+ self.fake_endpoint.service_id,
+ self.fake_endpoint.service_name,
+ self.fake_endpoint.service_type,
)
self.assertEqual(datalist, data)
diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py
index 38684aa..96731c0 100644
--- a/openstackclient/tests/identity/v2_0/test_project.py
+++ b/openstackclient/tests/identity/v2_0/test_project.py
@@ -13,26 +13,16 @@
# under the License.
#
-import copy
-
from keystoneauth1 import exceptions as ks_exc
+from osc_lib import exceptions
from openstackclient.identity.v2_0 import project
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
class TestProject(identity_fakes.TestIdentityv2):
- def setUp(self):
- super(TestProject, self).setUp()
-
- # Get a shortcut to the TenantManager Mock
- self.projects_mock = self.app.client_manager.identity.tenants
- self.projects_mock.reset_mock()
-
-
-class TestProjectCreate(TestProject):
+ fake_project = identity_fakes.FakeProject.create_one_project()
columns = (
'description',
@@ -41,32 +31,38 @@ class TestProjectCreate(TestProject):
'name',
)
datalist = (
- identity_fakes.project_description,
+ fake_project.description,
True,
- identity_fakes.project_id,
- identity_fakes.project_name,
+ fake_project.id,
+ fake_project.name,
)
def setUp(self):
+ super(TestProject, self).setUp()
+
+ # Get a shortcut to the TenantManager Mock
+ self.projects_mock = self.app.client_manager.identity.tenants
+ self.projects_mock.reset_mock()
+
+
+class TestProjectCreate(TestProject):
+
+ def setUp(self):
super(TestProjectCreate, self).setUp()
- self.projects_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.create.return_value = self.fake_project
# Get the command object to test
self.cmd = project.CreateProject(self.app, None)
def test_project_create_no_options(self):
arglist = [
- identity_fakes.project_name,
+ self.fake_project.name,
]
verifylist = [
('enable', False),
('disable', False),
- ('name', identity_fakes.project_name),
+ ('name', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -81,7 +77,7 @@ class TestProjectCreate(TestProject):
'enabled': True,
}
self.projects_mock.create.assert_called_with(
- identity_fakes.project_name,
+ self.fake_project.name,
**kwargs
)
self.assertEqual(self.columns, columns)
@@ -90,11 +86,11 @@ class TestProjectCreate(TestProject):
def test_project_create_description(self):
arglist = [
'--description', 'new desc',
- identity_fakes.project_name,
+ self.fake_project.name,
]
verifylist = [
('description', 'new desc'),
- ('name', identity_fakes.project_name),
+ ('name', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -109,7 +105,7 @@ class TestProjectCreate(TestProject):
'enabled': True,
}
self.projects_mock.create.assert_called_with(
- identity_fakes.project_name,
+ self.fake_project.name,
**kwargs
)
@@ -119,12 +115,12 @@ class TestProjectCreate(TestProject):
def test_project_create_enable(self):
arglist = [
'--enable',
- identity_fakes.project_name,
+ self.fake_project.name,
]
verifylist = [
('enable', True),
('disable', False),
- ('name', identity_fakes.project_name),
+ ('name', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -139,7 +135,7 @@ class TestProjectCreate(TestProject):
'enabled': True,
}
self.projects_mock.create.assert_called_with(
- identity_fakes.project_name,
+ self.fake_project.name,
**kwargs
)
@@ -149,12 +145,12 @@ class TestProjectCreate(TestProject):
def test_project_create_disable(self):
arglist = [
'--disable',
- identity_fakes.project_name,
+ self.fake_project.name,
]
verifylist = [
('enable', False),
('disable', True),
- ('name', identity_fakes.project_name),
+ ('name', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -169,7 +165,7 @@ class TestProjectCreate(TestProject):
'enabled': False,
}
self.projects_mock.create.assert_called_with(
- identity_fakes.project_name,
+ self.fake_project.name,
**kwargs
)
@@ -180,11 +176,11 @@ class TestProjectCreate(TestProject):
arglist = [
'--property', 'fee=fi',
'--property', 'fo=fum',
- identity_fakes.project_name,
+ self.fake_project.name,
]
verifylist = [
('property', {'fee': 'fi', 'fo': 'fum'}),
- ('name', identity_fakes.project_name),
+ ('name', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -201,7 +197,7 @@ class TestProjectCreate(TestProject):
'fo': 'fum',
}
self.projects_mock.create.assert_called_with(
- identity_fakes.project_name,
+ self.fake_project.name,
**kwargs
)
@@ -215,19 +211,15 @@ class TestProjectCreate(TestProject):
# need to make this throw an exception...
self.projects_mock.create.side_effect = _raise_conflict
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.fake_project
arglist = [
'--or-show',
- identity_fakes.project_name,
+ self.fake_project.name,
]
verifylist = [
- ('name', identity_fakes.project_name),
('or_show', True),
+ ('name', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -237,7 +229,7 @@ class TestProjectCreate(TestProject):
columns, data = self.cmd.take_action(parsed_args)
# ProjectManager.create(name, description, enabled)
- self.projects_mock.get.assert_called_with(identity_fakes.project_name)
+ self.projects_mock.get.assert_called_with(self.fake_project.name)
# Set expected values
kwargs = {
@@ -245,7 +237,7 @@ class TestProjectCreate(TestProject):
'enabled': True,
}
self.projects_mock.create.assert_called_with(
- identity_fakes.project_name,
+ self.fake_project.name,
**kwargs
)
@@ -255,11 +247,11 @@ class TestProjectCreate(TestProject):
def test_project_create_or_show_not_exists(self):
arglist = [
'--or-show',
- identity_fakes.project_name,
+ self.fake_project.name,
]
verifylist = [
- ('name', identity_fakes.project_name),
('or_show', True),
+ ('name', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -274,7 +266,7 @@ class TestProjectCreate(TestProject):
'enabled': True,
}
self.projects_mock.create.assert_called_with(
- identity_fakes.project_name,
+ self.fake_project.name,
**kwargs
)
@@ -288,11 +280,7 @@ class TestProjectDelete(TestProject):
super(TestProjectDelete, self).setUp()
# This is the return value for utils.find_resource()
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.fake_project
self.projects_mock.delete.return_value = None
# Get the command object to test
@@ -300,17 +288,17 @@ class TestProjectDelete(TestProject):
def test_project_delete_no_options(self):
arglist = [
- identity_fakes.project_id,
+ self.fake_project.id,
]
verifylist = [
- ('projects', [identity_fakes.project_id]),
+ ('projects', [self.fake_project.id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.projects_mock.delete.assert_called_with(
- identity_fakes.project_id,
+ self.fake_project.id,
)
self.assertIsNone(result)
@@ -320,13 +308,7 @@ class TestProjectList(TestProject):
def setUp(self):
super(TestProjectList, self).setUp()
- self.projects_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- ),
- ]
+ self.projects_mock.list.return_value = [self.fake_project]
# Get the command object to test
self.cmd = project.ListProject(self.app, None)
@@ -345,8 +327,8 @@ class TestProjectList(TestProject):
collist = ('ID', 'Name')
self.assertEqual(collist, columns)
datalist = ((
- identity_fakes.project_id,
- identity_fakes.project_name,
+ self.fake_project.id,
+ self.fake_project.name,
), )
self.assertEqual(datalist, tuple(data))
@@ -368,9 +350,9 @@ class TestProjectList(TestProject):
collist = ('ID', 'Name', 'Description', 'Enabled')
self.assertEqual(collist, columns)
datalist = ((
- identity_fakes.project_id,
- identity_fakes.project_name,
- identity_fakes.project_description,
+ self.fake_project.id,
+ self.fake_project.name,
+ self.fake_project.description,
True,
), )
self.assertEqual(datalist, tuple(data))
@@ -381,26 +363,18 @@ class TestProjectSet(TestProject):
def setUp(self):
super(TestProjectSet, self).setUp()
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
- self.projects_mock.update.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.fake_project
+ self.projects_mock.update.return_value = self.fake_project
# Get the command object to test
self.cmd = project.SetProject(self.app, None)
def test_project_set_no_options(self):
arglist = [
- identity_fakes.project_name,
+ self.fake_project.name,
]
verifylist = [
- ('project', identity_fakes.project_name),
+ ('project', self.fake_project.name),
('enable', False),
('disable', False),
]
@@ -410,16 +384,36 @@ class TestProjectSet(TestProject):
self.assertIsNone(result)
+ def test_project_set_unexist_project(self):
+ arglist = [
+ "unexist-project",
+ ]
+ verifylist = [
+ ('project', "unexist-project"),
+ ('name', None),
+ ('description', None),
+ ('enable', False),
+ ('disable', False),
+ ('property', None),
+ ]
+ self.projects_mock.get.side_effect = exceptions.NotFound(None)
+ self.projects_mock.find.side_effect = exceptions.NotFound(None)
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(
+ exceptions.CommandError, self.cmd.take_action, parsed_args)
+
def test_project_set_name(self):
arglist = [
- '--name', 'qwerty',
- identity_fakes.project_name,
+ '--name', self.fake_project.name,
+ self.fake_project.name,
]
verifylist = [
- ('name', 'qwerty'),
+ ('name', self.fake_project.name),
('enable', False),
('disable', False),
- ('project', identity_fakes.project_name),
+ ('project', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -427,26 +421,26 @@ class TestProjectSet(TestProject):
# Set expected values
kwargs = {
- 'description': identity_fakes.project_description,
+ 'description': self.fake_project.description,
'enabled': True,
- 'tenant_name': 'qwerty',
+ 'tenant_name': self.fake_project.name,
}
self.projects_mock.update.assert_called_with(
- identity_fakes.project_id,
+ self.fake_project.id,
**kwargs
)
self.assertIsNone(result)
def test_project_set_description(self):
arglist = [
- '--description', 'new desc',
- identity_fakes.project_name,
+ '--description', self.fake_project.description,
+ self.fake_project.name,
]
verifylist = [
- ('description', 'new desc'),
+ ('description', self.fake_project.description),
('enable', False),
('disable', False),
- ('project', identity_fakes.project_name),
+ ('project', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -454,12 +448,12 @@ class TestProjectSet(TestProject):
# Set expected values
kwargs = {
- 'description': 'new desc',
+ 'description': self.fake_project.description,
'enabled': True,
- 'tenant_name': identity_fakes.project_name,
+ 'tenant_name': self.fake_project.name,
}
self.projects_mock.update.assert_called_with(
- identity_fakes.project_id,
+ self.fake_project.id,
**kwargs
)
self.assertIsNone(result)
@@ -467,12 +461,12 @@ class TestProjectSet(TestProject):
def test_project_set_enable(self):
arglist = [
'--enable',
- identity_fakes.project_name,
+ self.fake_project.name,
]
verifylist = [
('enable', True),
('disable', False),
- ('project', identity_fakes.project_name),
+ ('project', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -480,12 +474,12 @@ class TestProjectSet(TestProject):
# Set expected values
kwargs = {
- 'description': identity_fakes.project_description,
+ 'description': self.fake_project.description,
'enabled': True,
- 'tenant_name': identity_fakes.project_name,
+ 'tenant_name': self.fake_project.name,
}
self.projects_mock.update.assert_called_with(
- identity_fakes.project_id,
+ self.fake_project.id,
**kwargs
)
self.assertIsNone(result)
@@ -493,12 +487,12 @@ class TestProjectSet(TestProject):
def test_project_set_disable(self):
arglist = [
'--disable',
- identity_fakes.project_name,
+ self.fake_project.name,
]
verifylist = [
('enable', False),
('disable', True),
- ('project', identity_fakes.project_name),
+ ('project', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -506,12 +500,12 @@ class TestProjectSet(TestProject):
# Set expected values
kwargs = {
- 'description': identity_fakes.project_description,
+ 'description': self.fake_project.description,
'enabled': False,
- 'tenant_name': identity_fakes.project_name,
+ 'tenant_name': self.fake_project.name,
}
self.projects_mock.update.assert_called_with(
- identity_fakes.project_id,
+ self.fake_project.id,
**kwargs
)
self.assertIsNone(result)
@@ -520,11 +514,11 @@ class TestProjectSet(TestProject):
arglist = [
'--property', 'fee=fi',
'--property', 'fo=fum',
- identity_fakes.project_name,
+ self.fake_project.name,
]
verifylist = [
('property', {'fee': 'fi', 'fo': 'fum'}),
- ('project', identity_fakes.project_name),
+ ('project', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -532,14 +526,14 @@ class TestProjectSet(TestProject):
# Set expected values
kwargs = {
- 'description': identity_fakes.project_description,
+ 'description': self.fake_project.description,
'enabled': True,
- 'tenant_name': identity_fakes.project_name,
+ 'tenant_name': self.fake_project.name,
'fee': 'fi',
'fo': 'fum',
}
self.projects_mock.update.assert_called_with(
- identity_fakes.project_id,
+ self.fake_project.id,
**kwargs
)
self.assertIsNone(result)
@@ -547,24 +541,22 @@ class TestProjectSet(TestProject):
class TestProjectShow(TestProject):
+ fake_proj_show = identity_fakes.FakeProject.create_one_project()
+
def setUp(self):
super(TestProjectShow, self).setUp()
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.fake_proj_show
# Get the command object to test
self.cmd = project.ShowProject(self.app, None)
def test_project_show(self):
arglist = [
- identity_fakes.project_id,
+ self.fake_proj_show.id,
]
verifylist = [
- ('project', identity_fakes.project_id),
+ ('project', self.fake_proj_show.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -573,16 +565,16 @@ class TestProjectShow(TestProject):
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
self.projects_mock.get.assert_called_with(
- identity_fakes.project_id,
+ self.fake_proj_show.id,
)
collist = ('description', 'enabled', 'id', 'name', 'properties')
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.project_description,
+ self.fake_proj_show.description,
True,
- identity_fakes.project_id,
- identity_fakes.project_name,
+ self.fake_proj_show.id,
+ self.fake_proj_show.name,
'',
)
self.assertEqual(datalist, data)
@@ -590,25 +582,35 @@ class TestProjectShow(TestProject):
class TestProjectUnset(TestProject):
+ attr = {'fee': 'fi', 'fo': 'fum'}
+ fake_proj = identity_fakes.FakeProject.create_one_project(attr)
+
def setUp(self):
super(TestProjectUnset, self).setUp()
- project_dict = {'fee': 'fi', 'fo': 'fum'}
- project_dict.update(identity_fakes.PROJECT)
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(project_dict),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.fake_proj
# Get the command object to test
self.cmd = project.UnsetProject(self.app, None)
+ def test_project_unset_no_options(self):
+ arglist = [
+ self.fake_proj.name,
+ ]
+ verifylist = [
+ ('project', self.fake_proj.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.assertIsNone(result)
+
def test_project_unset_key(self):
arglist = [
'--property', 'fee',
'--property', 'fo',
- identity_fakes.project_name,
+ self.fake_proj.name,
]
verifylist = [
('property', ['fee', 'fo']),
@@ -618,16 +620,16 @@ class TestProjectUnset(TestProject):
result = self.cmd.take_action(parsed_args)
# Set expected values
kwargs = {
- 'description': identity_fakes.project_description,
+ 'description': self.fake_proj.description,
'enabled': True,
'fee': None,
'fo': None,
- 'id': identity_fakes.project_id,
- 'name': identity_fakes.project_name,
+ 'id': self.fake_proj.id,
+ 'name': self.fake_proj.name,
}
self.projects_mock.update.assert_called_with(
- identity_fakes.project_id,
+ self.fake_proj.id,
**kwargs
)
self.assertIsNone(result)
diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py
index 3c4b79a..3d37935 100644
--- a/openstackclient/tests/identity/v2_0/test_role.py
+++ b/openstackclient/tests/identity/v2_0/test_role.py
@@ -13,19 +13,32 @@
# under the License.
#
-import copy
import mock
from keystoneauth1 import exceptions as ks_exc
+from osc_lib import exceptions
-from openstackclient.common import exceptions
from openstackclient.identity.v2_0 import role
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
class TestRole(identity_fakes.TestIdentityv2):
+ attr = {}
+ attr['endpoints'] = [
+ {
+ 'publicURL': identity_fakes.ENDPOINT['publicurl'],
+ },
+ ]
+ fake_service = identity_fakes.FakeService.create_one_service(attr)
+ fake_role = identity_fakes.FakeRole.create_one_role()
+ fake_project = identity_fakes.FakeProject.create_one_project()
+ attr = {}
+ attr = {
+ 'tenantId': fake_project.id,
+ }
+ fake_user = identity_fakes.FakeUser.create_one_user(attr)
+
def setUp(self):
super(TestRole, self).setUp()
@@ -41,48 +54,39 @@ class TestRole(identity_fakes.TestIdentityv2):
self.roles_mock = self.app.client_manager.identity.roles
self.roles_mock.reset_mock()
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.TOKEN,
+ fake_service=self.fake_service,
+ )
+ self.ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = self.ar_mock
+
class TestRoleAdd(TestRole):
def setUp(self):
super(TestRoleAdd, self).setUp()
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.fake_project
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.users_mock.get.return_value = self.fake_user
- self.roles_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ROLE),
- loaded=True,
- )
- self.roles_mock.add_user_role.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ROLE),
- loaded=True,
- )
+ self.roles_mock.get.return_value = self.fake_role
+ self.roles_mock.add_user_role.return_value = self.fake_role
# Get the command object to test
self.cmd = role.AddRole(self.app, None)
def test_role_add(self):
arglist = [
- '--project', identity_fakes.project_name,
- '--user', identity_fakes.user_name,
- identity_fakes.role_name,
+ '--project', self.fake_project.name,
+ '--user', self.fake_user.name,
+ self.fake_role.name,
]
verifylist = [
- ('project', identity_fakes.project_name),
- ('user', identity_fakes.user_name),
- ('role', identity_fakes.role_name),
+ ('project', self.fake_project.name),
+ ('user', self.fake_user.name),
+ ('role', self.fake_role.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -93,49 +97,46 @@ class TestRoleAdd(TestRole):
# RoleManager.add_user_role(user, role, tenant=None)
self.roles_mock.add_user_role.assert_called_with(
- identity_fakes.user_id,
- identity_fakes.role_id,
- identity_fakes.project_id,
+ self.fake_user.id,
+ self.fake_role.id,
+ self.fake_project.id,
)
collist = ('id', 'name')
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.role_id,
- identity_fakes.role_name,
+ self.fake_role.id,
+ self.fake_role.name,
)
self.assertEqual(datalist, data)
class TestRoleCreate(TestRole):
+ fake_role_c = identity_fakes.FakeRole.create_one_role()
columns = (
'id',
'name'
)
datalist = (
- identity_fakes.role_id,
- identity_fakes.role_name,
+ fake_role_c.id,
+ fake_role_c.name,
)
def setUp(self):
super(TestRoleCreate, self).setUp()
- self.roles_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ROLE),
- loaded=True,
- )
+ self.roles_mock.create.return_value = self.fake_role_c
# Get the command object to test
self.cmd = role.CreateRole(self.app, None)
def test_role_create_no_options(self):
arglist = [
- identity_fakes.role_name,
+ self.fake_role_c.name,
]
verifylist = [
- ('role_name', identity_fakes.role_name),
+ ('role_name', self.fake_role_c.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -146,7 +147,7 @@ class TestRoleCreate(TestRole):
# RoleManager.create(name)
self.roles_mock.create.assert_called_with(
- identity_fakes.role_name,
+ self.fake_role_c.name,
)
self.assertEqual(self.columns, columns)
@@ -159,18 +160,14 @@ class TestRoleCreate(TestRole):
# need to make this throw an exception...
self.roles_mock.create.side_effect = _raise_conflict
- self.roles_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ROLE),
- loaded=True,
- )
+ self.roles_mock.get.return_value = self.fake_role_c
arglist = [
'--or-show',
- identity_fakes.role_name,
+ self.fake_role_c.name,
]
verifylist = [
- ('role_name', identity_fakes.role_name),
+ ('role_name', self.fake_role_c.name),
('or_show', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -181,11 +178,11 @@ class TestRoleCreate(TestRole):
columns, data = self.cmd.take_action(parsed_args)
# RoleManager.get(name, description, enabled)
- self.roles_mock.get.assert_called_with(identity_fakes.role_name)
+ self.roles_mock.get.assert_called_with(self.fake_role_c.name)
# RoleManager.create(name)
self.roles_mock.create.assert_called_with(
- identity_fakes.role_name,
+ self.fake_role_c.name,
)
self.assertEqual(self.columns, columns)
@@ -194,10 +191,10 @@ class TestRoleCreate(TestRole):
def test_role_create_or_show_not_exists(self):
arglist = [
'--or-show',
- identity_fakes.role_name,
+ self.fake_role_c.name,
]
verifylist = [
- ('role_name', identity_fakes.role_name),
+ ('role_name', self.fake_role_c.name),
('or_show', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -209,7 +206,7 @@ class TestRoleCreate(TestRole):
# RoleManager.create(name)
self.roles_mock.create.assert_called_with(
- identity_fakes.role_name,
+ self.fake_role_c.name,
)
self.assertEqual(self.columns, columns)
@@ -221,11 +218,7 @@ class TestRoleDelete(TestRole):
def setUp(self):
super(TestRoleDelete, self).setUp()
- self.roles_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ROLE),
- loaded=True,
- )
+ self.roles_mock.get.return_value = self.fake_role
self.roles_mock.delete.return_value = None
# Get the command object to test
@@ -233,17 +226,17 @@ class TestRoleDelete(TestRole):
def test_role_delete_no_options(self):
arglist = [
- identity_fakes.role_name,
+ self.fake_role.name,
]
verifylist = [
- ('roles', [identity_fakes.role_name]),
+ ('roles', [self.fake_role.name]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.roles_mock.delete.assert_called_with(
- identity_fakes.role_id,
+ self.fake_role.id,
)
self.assertIsNone(result)
@@ -253,13 +246,7 @@ class TestRoleList(TestRole):
def setUp(self):
super(TestRoleList, self).setUp()
- self.roles_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ROLE),
- loaded=True,
- ),
- ]
+ self.roles_mock.list.return_value = [self.fake_role]
# Get the command object to test
self.cmd = role.ListRole(self.app, None)
@@ -279,8 +266,8 @@ class TestRoleList(TestRole):
collist = ('ID', 'Name')
self.assertEqual(collist, columns)
datalist = ((
- identity_fakes.role_id,
- identity_fakes.role_name,
+ self.fake_role.id,
+ self.fake_role.name,
), )
self.assertEqual(datalist, tuple(data))
@@ -297,30 +284,23 @@ class TestUserRoleList(TestRole):
def setUp(self):
super(TestUserRoleList, self).setUp()
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.fake_project
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.users_mock.get.return_value = self.fake_user
- self.roles_mock.roles_for_user.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ROLE),
- loaded=True,
- ),
- ]
+ self.roles_mock.roles_for_user.return_value = [self.fake_role]
# Get the command object to test
self.cmd = role.ListUserRole(self.app, None)
- def test_user_role_list_no_options(self):
+ def test_user_role_list_no_options_unscoped_token(self):
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.UNSCOPED_TOKEN,
+ fake_service=self.fake_service,
+ )
+ self.ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = self.ar_mock
+
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -332,11 +312,7 @@ class TestUserRoleList(TestRole):
parsed_args,
)
- def test_user_role_list_no_options_def_creds(self):
- auth_ref = self.app.client_manager.auth_ref = mock.MagicMock()
- auth_ref.project_id.return_value = identity_fakes.project_id
- auth_ref.user_id.return_value = identity_fakes.user_id
-
+ def test_user_role_list_no_options_scoped_token(self):
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -347,56 +323,63 @@ class TestUserRoleList(TestRole):
columns, data = self.cmd.take_action(parsed_args)
self.roles_mock.roles_for_user.assert_called_with(
- identity_fakes.user_id,
- identity_fakes.project_id,
+ self.fake_user.id,
+ self.fake_project.id,
)
collist = ('ID', 'Name', 'Project', 'User')
self.assertEqual(collist, columns)
datalist = ((
- identity_fakes.role_id,
- identity_fakes.role_name,
- identity_fakes.project_name,
- identity_fakes.user_name,
+ self.fake_role.id,
+ self.fake_role.name,
+ self.fake_project.name,
+ self.fake_user.name,
), )
self.assertEqual(datalist, tuple(data))
- def test_user_role_list_project(self):
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT_2),
- loaded=True,
+ def test_user_role_list_project_unscoped_token(self):
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.UNSCOPED_TOKEN,
+ fake_service=self.fake_service,
)
+ self.ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = self.ar_mock
+
+ self.projects_mock.get.return_value = self.fake_project
arglist = [
- '--project', identity_fakes.PROJECT_2['name'],
+ '--project', self.fake_project.name,
]
verifylist = [
- ('project', identity_fakes.PROJECT_2['name']),
+ ('project', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- # This argument combination should raise a CommandError
- self.assertRaises(
- exceptions.CommandError,
- self.cmd.take_action,
- parsed_args,
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.roles_mock.roles_for_user.assert_called_with(
+ self.fake_user.id,
+ self.fake_project.id,
)
- def test_user_role_list_project_def_creds(self):
- auth_ref = self.app.client_manager.auth_ref = mock.MagicMock()
- auth_ref.project_id.return_value = identity_fakes.project_id
- auth_ref.user_id.return_value = identity_fakes.user_id
+ self.assertEqual(columns, columns)
+ datalist = ((
+ self.fake_role.id,
+ self.fake_role.name,
+ self.fake_project.name,
+ self.fake_user.name,
+ ), )
+ self.assertEqual(datalist, tuple(data))
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT_2),
- loaded=True,
- )
+ def test_user_role_list_project_scoped_token(self):
+ self.projects_mock.get.return_value = self.fake_project
arglist = [
- '--project', identity_fakes.PROJECT_2['name'],
+ '--project', self.fake_project.name,
]
verifylist = [
- ('project', identity_fakes.PROJECT_2['name']),
+ ('project', self.fake_project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -406,16 +389,16 @@ class TestUserRoleList(TestRole):
columns, data = self.cmd.take_action(parsed_args)
self.roles_mock.roles_for_user.assert_called_with(
- identity_fakes.user_id,
- identity_fakes.PROJECT_2['id'],
+ self.fake_user.id,
+ self.fake_project.id,
)
self.assertEqual(columns, columns)
datalist = ((
- identity_fakes.role_id,
- identity_fakes.role_name,
- identity_fakes.PROJECT_2['name'],
- identity_fakes.user_name,
+ self.fake_role.id,
+ self.fake_role.name,
+ self.fake_project.name,
+ self.fake_user.name,
), )
self.assertEqual(datalist, tuple(data))
@@ -425,23 +408,11 @@ class TestRoleRemove(TestRole):
def setUp(self):
super(TestRoleRemove, self).setUp()
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.fake_project
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.users_mock.get.return_value = self.fake_user
- self.roles_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ROLE),
- loaded=True,
- )
+ self.roles_mock.get.return_value = self.fake_role
self.roles_mock.remove_user_role.return_value = None
# Get the command object to test
@@ -449,14 +420,14 @@ class TestRoleRemove(TestRole):
def test_role_remove(self):
arglist = [
- '--project', identity_fakes.project_name,
- '--user', identity_fakes.user_name,
- identity_fakes.role_name,
+ '--project', self.fake_project.name,
+ '--user', self.fake_user.name,
+ self.fake_role.name,
]
verifylist = [
- ('role', identity_fakes.role_name),
- ('project', identity_fakes.project_name),
- ('user', identity_fakes.user_name),
+ ('role', self.fake_role.name),
+ ('project', self.fake_project.name),
+ ('user', self.fake_user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -464,9 +435,9 @@ class TestRoleRemove(TestRole):
# RoleManager.remove_user_role(user, role, tenant=None)
self.roles_mock.remove_user_role.assert_called_with(
- identity_fakes.user_id,
- identity_fakes.role_id,
- identity_fakes.project_id,
+ self.fake_user.id,
+ self.fake_role.id,
+ self.fake_project.id,
)
self.assertIsNone(result)
@@ -476,21 +447,17 @@ class TestRoleShow(TestRole):
def setUp(self):
super(TestRoleShow, self).setUp()
- self.roles_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ROLE),
- loaded=True,
- )
+ self.roles_mock.get.return_value = self.fake_role
# Get the command object to test
self.cmd = role.ShowRole(self.app, None)
def test_service_show(self):
arglist = [
- identity_fakes.role_name,
+ self.fake_role.name,
]
verifylist = [
- ('role', identity_fakes.role_name),
+ ('role', self.fake_role.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -501,13 +468,13 @@ class TestRoleShow(TestRole):
# RoleManager.get(role)
self.roles_mock.get.assert_called_with(
- identity_fakes.role_name,
+ self.fake_role.name,
)
collist = ('id', 'name')
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.role_id,
- identity_fakes.role_name,
+ self.fake_role.id,
+ self.fake_role.name,
)
self.assertEqual(datalist, data)
diff --git a/openstackclient/tests/identity/v2_0/test_role_assignment.py b/openstackclient/tests/identity/v2_0/test_role_assignment.py
new file mode 100644
index 0000000..a356ae0
--- /dev/null
+++ b/openstackclient/tests/identity/v2_0/test_role_assignment.py
@@ -0,0 +1,271 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import copy
+import mock
+
+from osc_lib import exceptions
+
+from openstackclient.identity.v2_0 import role_assignment
+from openstackclient.tests import fakes
+from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
+
+
+class TestRoleAssignment(identity_fakes.TestIdentityv2):
+
+ def setUp(self):
+ super(TestRoleAssignment, self).setUp()
+
+
+class TestRoleAssignmentList(TestRoleAssignment):
+
+ columns = (
+ 'Role',
+ 'User',
+ 'Project',
+ )
+
+ def setUp(self):
+ super(TestRoleAssignment, self).setUp()
+
+ # Get a shortcut to the UserManager Mock
+ self.users_mock = self.app.client_manager.identity.users
+ self.users_mock.reset_mock()
+
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+ self.projects_mock.reset_mock()
+
+ # Get a shortcut to the RoleManager Mock
+ self.roles_mock = self.app.client_manager.identity.roles
+ self.roles_mock.reset_mock()
+
+ self.projects_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.PROJECT),
+ loaded=True,
+ )
+
+ self.users_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.USER),
+ loaded=True,
+ )
+
+ self.roles_mock.roles_for_user.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.ROLE),
+ loaded=True,
+ ),
+ ]
+
+ # Get the command object to test
+ self.cmd = role_assignment.ListRoleAssignment(self.app, None)
+
+ def test_role_assignment_list_no_filters(self):
+
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # This argument combination should raise a CommandError
+ self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args,
+ )
+
+ def test_role_assignment_list_only_project_filter(self):
+
+ arglist = [
+ '--project', identity_fakes.project_name,
+ ]
+ verifylist = [
+ ('project', identity_fakes.project_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # This argument combination should raise a CommandError
+ self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args,
+ )
+
+ def test_role_assignment_list_only_user_filter(self):
+
+ arglist = [
+ '--user', identity_fakes.user_name,
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # This argument combination should raise a CommandError
+ self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args,
+ )
+
+ def test_role_assignment_list_project_and_user(self):
+
+ self.roles_mock.roles_for_user.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ROLE),
+ loaded=True,
+ ),
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ROLE_2),
+ loaded=True,
+ ),
+ ]
+
+ arglist = [
+ '--project', identity_fakes.project_name,
+ '--user', identity_fakes.user_name,
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('project', identity_fakes.project_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.roles_mock.roles_for_user.assert_called_with(
+ identity_fakes.user_id,
+ identity_fakes.project_id,
+ )
+
+ self.assertEqual(self.columns, columns)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.user_id,
+ identity_fakes.project_id,
+ ), (identity_fakes.ROLE_2['id'],
+ identity_fakes.user_id,
+ identity_fakes.project_id,
+ ),)
+ self.assertEqual(datalist, tuple(data))
+
+ def test_role_assignment_list_def_creds(self):
+
+ auth_ref = self.app.client_manager.auth_ref = mock.MagicMock()
+ auth_ref.project_id.return_value = identity_fakes.project_id
+ auth_ref.user_id.return_value = identity_fakes.user_id
+
+ self.roles_mock.roles_for_user.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ROLE),
+ loaded=True,
+ ),
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ROLE_2),
+ loaded=True,
+ ),
+ ]
+
+ arglist = [
+ '--auth-user',
+ '--auth-project',
+ ]
+ verifylist = [
+ ('authuser', True),
+ ('authproject', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.roles_mock.roles_for_user.assert_called_with(
+ identity_fakes.user_id,
+ identity_fakes.project_id,
+ )
+
+ self.assertEqual(self.columns, columns)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.user_id,
+ identity_fakes.project_id,
+ ), (identity_fakes.ROLE_2['id'],
+ identity_fakes.user_id,
+ identity_fakes.project_id,
+ ),)
+ self.assertEqual(datalist, tuple(data))
+
+ def test_role_assignment_list_by_name_project_and_user(self):
+
+ self.roles_mock.roles_for_user.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ROLE),
+ loaded=True,
+ ),
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ROLE_2),
+ loaded=True,
+ ),
+ ]
+
+ arglist = [
+ '--project', identity_fakes.project_name,
+ '--user', identity_fakes.user_name,
+ '--names'
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('project', identity_fakes.project_name),
+ ('names', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.roles_mock.roles_for_user.assert_called_with(
+ identity_fakes.user_id,
+ identity_fakes.project_id,
+ )
+
+ self.assertEqual(self.columns, columns)
+ datalist = ((
+ identity_fakes.role_name,
+ identity_fakes.user_name,
+ identity_fakes.project_name,
+ ), (identity_fakes.ROLE_2['name'],
+ identity_fakes.user_name,
+ identity_fakes.project_name,
+ ),)
+ self.assertEqual(datalist, tuple(data))
diff --git a/openstackclient/tests/identity/v2_0/test_service.py b/openstackclient/tests/identity/v2_0/test_service.py
index dc0fbcd..7efd2a6 100644
--- a/openstackclient/tests/identity/v2_0/test_service.py
+++ b/openstackclient/tests/identity/v2_0/test_service.py
@@ -13,14 +13,15 @@
# under the License.
#
-import copy
+from keystoneclient import exceptions as identity_exc
+from osc_lib import exceptions
from openstackclient.identity.v2_0 import service
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
class TestService(identity_fakes.TestIdentityv2):
+ fake_service = identity_fakes.FakeService.create_one_service()
def setUp(self):
super(TestService, self).setUp()
@@ -32,6 +33,7 @@ class TestService(identity_fakes.TestIdentityv2):
class TestServiceCreate(TestService):
+ fake_service_c = identity_fakes.FakeService.create_one_service()
columns = (
'description',
'id',
@@ -39,30 +41,26 @@ class TestServiceCreate(TestService):
'type',
)
datalist = (
- identity_fakes.service_description,
- identity_fakes.service_id,
- identity_fakes.service_name,
- identity_fakes.service_type,
+ fake_service_c.description,
+ fake_service_c.id,
+ fake_service_c.name,
+ fake_service_c.type,
)
def setUp(self):
super(TestServiceCreate, self).setUp()
- self.services_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
+ self.services_mock.create.return_value = self.fake_service_c
# Get the command object to test
self.cmd = service.CreateService(self.app, None)
def test_service_create_with_type_positional(self):
arglist = [
- identity_fakes.service_type,
+ self.fake_service_c.type,
]
verifylist = [
- ('type_or_name', identity_fakes.service_type),
+ ('type_or_name', self.fake_service_c.type),
('type', None),
('description', None),
('name', None),
@@ -77,7 +75,7 @@ class TestServiceCreate(TestService):
# ServiceManager.create(name, service_type, description)
self.services_mock.create.assert_called_with(
None,
- identity_fakes.service_type,
+ self.fake_service_c.type,
None,
)
@@ -86,12 +84,12 @@ class TestServiceCreate(TestService):
def test_service_create_with_type_option(self):
arglist = [
- '--type', identity_fakes.service_type,
- identity_fakes.service_name,
+ '--type', self.fake_service_c.type,
+ self.fake_service_c.name,
]
verifylist = [
- ('type_or_name', identity_fakes.service_name),
- ('type', identity_fakes.service_type),
+ ('type_or_name', self.fake_service_c.name),
+ ('type', self.fake_service_c.type),
('description', None),
('name', None),
]
@@ -104,8 +102,8 @@ class TestServiceCreate(TestService):
# ServiceManager.create(name, service_type, description)
self.services_mock.create.assert_called_with(
- identity_fakes.service_name,
- identity_fakes.service_type,
+ self.fake_service_c.name,
+ self.fake_service_c.type,
None,
)
@@ -114,14 +112,14 @@ class TestServiceCreate(TestService):
def test_service_create_with_name_option(self):
arglist = [
- '--name', identity_fakes.service_name,
- identity_fakes.service_type,
+ '--name', self.fake_service_c.name,
+ self.fake_service_c.type,
]
verifylist = [
- ('type_or_name', identity_fakes.service_type),
+ ('type_or_name', self.fake_service_c.type),
('type', None),
('description', None),
- ('name', identity_fakes.service_name),
+ ('name', self.fake_service_c.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -132,8 +130,8 @@ class TestServiceCreate(TestService):
# ServiceManager.create(name, service_type, description)
self.services_mock.create.assert_called_with(
- identity_fakes.service_name,
- identity_fakes.service_type,
+ self.fake_service_c.name,
+ self.fake_service_c.type,
None,
)
@@ -142,15 +140,15 @@ class TestServiceCreate(TestService):
def test_service_create_description(self):
arglist = [
- '--name', identity_fakes.service_name,
- '--description', identity_fakes.service_description,
- identity_fakes.service_type,
+ '--name', self.fake_service_c.name,
+ '--description', self.fake_service_c.description,
+ self.fake_service_c.type,
]
verifylist = [
- ('type_or_name', identity_fakes.service_type),
+ ('type_or_name', self.fake_service_c.type),
('type', None),
- ('description', identity_fakes.service_description),
- ('name', identity_fakes.service_name),
+ ('description', self.fake_service_c.description),
+ ('name', self.fake_service_c.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -161,9 +159,9 @@ class TestServiceCreate(TestService):
# ServiceManager.create(name, service_type, description)
self.services_mock.create.assert_called_with(
- identity_fakes.service_name,
- identity_fakes.service_type,
- identity_fakes.service_description,
+ self.fake_service_c.name,
+ self.fake_service_c.type,
+ self.fake_service_c.description,
)
self.assertEqual(self.columns, columns)
@@ -175,11 +173,8 @@ class TestServiceDelete(TestService):
def setUp(self):
super(TestServiceDelete, self).setUp()
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
+ self.services_mock.get.side_effect = identity_exc.NotFound(None)
+ self.services_mock.find.return_value = self.fake_service
self.services_mock.delete.return_value = None
# Get the command object to test
@@ -187,17 +182,17 @@ class TestServiceDelete(TestService):
def test_service_delete_no_options(self):
arglist = [
- identity_fakes.service_name,
+ self.fake_service.name,
]
verifylist = [
- ('service', identity_fakes.service_name),
+ ('services', [self.fake_service.name]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.services_mock.delete.assert_called_with(
- identity_fakes.service_id,
+ self.fake_service.id,
)
self.assertIsNone(result)
@@ -207,13 +202,7 @@ class TestServiceList(TestService):
def setUp(self):
super(TestServiceList, self).setUp()
- self.services_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- ),
- ]
+ self.services_mock.list.return_value = [self.fake_service]
# Get the command object to test
self.cmd = service.ListService(self.app, None)
@@ -233,9 +222,9 @@ class TestServiceList(TestService):
collist = ('ID', 'Name', 'Type')
self.assertEqual(collist, columns)
datalist = ((
- identity_fakes.service_id,
- identity_fakes.service_name,
- identity_fakes.service_type,
+ self.fake_service.id,
+ self.fake_service.name,
+ self.fake_service.type,
), )
self.assertEqual(datalist, tuple(data))
@@ -258,34 +247,33 @@ class TestServiceList(TestService):
collist = ('ID', 'Name', 'Type', 'Description')
self.assertEqual(collist, columns)
datalist = ((
- identity_fakes.service_id,
- identity_fakes.service_name,
- identity_fakes.service_type,
- identity_fakes.service_description,
+ self.fake_service.id,
+ self.fake_service.name,
+ self.fake_service.type,
+ self.fake_service.description,
), )
self.assertEqual(datalist, tuple(data))
class TestServiceShow(TestService):
+ fake_service_s = identity_fakes.FakeService.create_one_service()
+
def setUp(self):
super(TestServiceShow, self).setUp()
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
+ self.services_mock.get.side_effect = identity_exc.NotFound(None)
+ self.services_mock.find.return_value = self.fake_service_s
# Get the command object to test
self.cmd = service.ShowService(self.app, None)
def test_service_show(self):
arglist = [
- identity_fakes.service_name,
+ self.fake_service_s.name,
]
verifylist = [
- ('service', identity_fakes.service_name),
+ ('service', self.fake_service_s.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -294,17 +282,35 @@ class TestServiceShow(TestService):
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
- # ServiceManager.get(id)
- self.services_mock.get.assert_called_with(
- identity_fakes.service_name,
+ # ServiceManager.find(id)
+ self.services_mock.find.assert_called_with(
+ name=self.fake_service_s.name,
)
collist = ('description', 'id', 'name', 'type')
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.service_description,
- identity_fakes.service_id,
- identity_fakes.service_name,
- identity_fakes.service_type,
+ self.fake_service_s.description,
+ self.fake_service_s.id,
+ self.fake_service_s.name,
+ self.fake_service_s.type,
)
self.assertEqual(datalist, data)
+
+ def test_service_show_nounique(self):
+ self.services_mock.find.side_effect = identity_exc.NoUniqueMatch(None)
+ arglist = [
+ 'nounique_service',
+ ]
+ verifylist = [
+ ('service', 'nounique_service'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual(
+ "Multiple service matches found for 'nounique_service',"
+ " use an ID to be more specific.", str(e))
diff --git a/openstackclient/tests/identity/v2_0/test_token.py b/openstackclient/tests/identity/v2_0/test_token.py
index 613139d..bb77670 100644
--- a/openstackclient/tests/identity/v2_0/test_token.py
+++ b/openstackclient/tests/identity/v2_0/test_token.py
@@ -21,13 +21,15 @@ from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
class TestToken(identity_fakes.TestIdentityv2):
+ fake_user = identity_fakes.FakeUser.create_one_user()
+ fake_project = identity_fakes.FakeProject.create_one_project()
+
def setUp(self):
super(TestToken, self).setUp()
- # Get a shortcut to the Service Catalog Mock
- self.sc_mock = mock.Mock()
- self.app.client_manager.auth_ref = mock.Mock()
- self.app.client_manager.auth_ref.service_catalog = self.sc_mock
+ # Get a shortcut to the Auth Ref Mock
+ self.ar_mock = mock.PropertyMock()
+ type(self.app.client_manager).auth_ref = self.ar_mock
class TestTokenIssue(TestToken):
@@ -35,10 +37,15 @@ class TestTokenIssue(TestToken):
def setUp(self):
super(TestTokenIssue, self).setUp()
- self.sc_mock.get_token.return_value = identity_fakes.TOKEN
self.cmd = token.IssueToken(self.app, None)
def test_token_issue(self):
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.TOKEN,
+ )
+ self.ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = self.ar_mock
+
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -48,21 +55,22 @@ class TestTokenIssue(TestToken):
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
- self.sc_mock.get_token.assert_called_with()
-
collist = ('expires', 'id', 'project_id', 'user_id')
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.token_expires,
+ auth_ref.expires,
identity_fakes.token_id,
- identity_fakes.project_id,
- identity_fakes.user_id,
+ 'project-id',
+ 'user-id',
)
self.assertEqual(datalist, data)
def test_token_issue_with_unscoped_token(self):
- # make sure we return an unscoped token
- self.sc_mock.get_token.return_value = identity_fakes.UNSCOPED_TOKEN
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.UNSCOPED_TOKEN,
+ )
+ self.ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = self.ar_mock
arglist = []
verifylist = []
@@ -71,14 +79,16 @@ class TestTokenIssue(TestToken):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.sc_mock.get_token.assert_called_with()
-
- collist = ('expires', 'id', 'user_id')
+ collist = (
+ 'expires',
+ 'id',
+ 'user_id',
+ )
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.token_expires,
+ auth_ref.expires,
identity_fakes.token_id,
- identity_fakes.user_id,
+ 'user-id',
)
self.assertEqual(datalist, data)
diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py
index 921e215..ba87124 100644
--- a/openstackclient/tests/identity/v2_0/test_user.py
+++ b/openstackclient/tests/identity/v2_0/test_user.py
@@ -13,18 +13,23 @@
# under the License.
#
-import copy
import mock
from keystoneauth1 import exceptions as ks_exc
+from osc_lib import exceptions
from openstackclient.identity.v2_0 import user
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
class TestUser(identity_fakes.TestIdentityv2):
+ fake_project = identity_fakes.FakeProject.create_one_project()
+ attr = {
+ 'tenantId': fake_project.id,
+ }
+ fake_user = identity_fakes.FakeUser.create_one_user(attr)
+
def setUp(self):
super(TestUser, self).setUp()
@@ -39,6 +44,12 @@ class TestUser(identity_fakes.TestIdentityv2):
class TestUserCreate(TestUser):
+ fake_project_c = identity_fakes.FakeProject.create_one_project()
+ attr = {
+ 'tenantId': fake_project_c.id,
+ }
+ fake_user_c = identity_fakes.FakeUser.create_one_user(attr)
+
columns = (
'email',
'enabled',
@@ -47,39 +58,31 @@ class TestUserCreate(TestUser):
'project_id',
)
datalist = (
- identity_fakes.user_email,
+ fake_user_c.email,
True,
- identity_fakes.user_id,
- identity_fakes.user_name,
- identity_fakes.project_id,
+ fake_user_c.id,
+ fake_user_c.name,
+ fake_project_c.id,
)
def setUp(self):
super(TestUserCreate, self).setUp()
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.fake_project_c
- self.users_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.users_mock.create.return_value = self.fake_user_c
# Get the command object to test
self.cmd = user.CreateUser(self.app, None)
def test_user_create_no_options(self):
arglist = [
- identity_fakes.user_name,
+ self.fake_user_c.name,
]
verifylist = [
('enable', False),
('disable', False),
- ('name', identity_fakes.user_name),
+ ('name', self.fake_user_c.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -95,7 +98,7 @@ class TestUserCreate(TestUser):
}
# UserManager.create(name, password, email, tenant_id=, enabled=)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
+ self.fake_user_c.name,
None,
None,
**kwargs
@@ -107,10 +110,10 @@ class TestUserCreate(TestUser):
def test_user_create_password(self):
arglist = [
'--password', 'secret',
- identity_fakes.user_name,
+ self.fake_user_c.name,
]
verifylist = [
- ('name', identity_fakes.user_name),
+ ('name', self.fake_user_c.name),
('password_prompt', False),
('password', 'secret')
]
@@ -128,7 +131,7 @@ class TestUserCreate(TestUser):
}
# UserManager.create(name, password, email, tenant_id=, enabled=)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
+ self.fake_user_c.name,
'secret',
None,
**kwargs
@@ -139,10 +142,10 @@ class TestUserCreate(TestUser):
def test_user_create_password_prompt(self):
arglist = [
'--password-prompt',
- identity_fakes.user_name,
+ self.fake_user_c.name,
]
verifylist = [
- ('name', identity_fakes.user_name),
+ ('name', self.fake_user_c.name),
('password_prompt', True)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -152,7 +155,7 @@ class TestUserCreate(TestUser):
# data to be shown.
mocker = mock.Mock()
mocker.return_value = 'abc123'
- with mock.patch("openstackclient.common.utils.get_password", mocker):
+ with mock.patch("osc_lib.utils.get_password", mocker):
columns, data = self.cmd.take_action(parsed_args)
# Set expected values
@@ -162,7 +165,7 @@ class TestUserCreate(TestUser):
}
# UserManager.create(name, password, email, tenant_id=, enabled=)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
+ self.fake_user_c.name,
'abc123',
None,
**kwargs
@@ -174,10 +177,10 @@ class TestUserCreate(TestUser):
def test_user_create_email(self):
arglist = [
'--email', 'barney@example.com',
- identity_fakes.user_name,
+ self.fake_user_c.name,
]
verifylist = [
- ('name', identity_fakes.user_name),
+ ('name', self.fake_user_c.name),
('email', 'barney@example.com'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -194,7 +197,7 @@ class TestUserCreate(TestUser):
}
# UserManager.create(name, password, email, tenant_id=, enabled=)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
+ self.fake_user_c.name,
None,
'barney@example.com',
**kwargs
@@ -205,27 +208,22 @@ class TestUserCreate(TestUser):
def test_user_create_project(self):
# Return the new project
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT_2),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.fake_project_c
+
# Set up to return an updated user
- USER_2 = copy.deepcopy(identity_fakes.USER)
- USER_2['tenantId'] = identity_fakes.PROJECT_2['id']
- self.users_mock.create.return_value = fakes.FakeResource(
- None,
- USER_2,
- loaded=True,
- )
+ attr = {
+ 'tenantId': self.fake_project_c.id,
+ }
+ user_2 = identity_fakes.FakeUser.create_one_user(attr)
+ self.users_mock.create.return_value = user_2
arglist = [
- '--project', identity_fakes.PROJECT_2['name'],
- identity_fakes.user_name,
+ '--project', self.fake_project_c.name,
+ user_2.name,
]
verifylist = [
- ('name', identity_fakes.user_name),
- ('project', identity_fakes.PROJECT_2['name']),
+ ('name', user_2.name),
+ ('project', self.fake_project_c.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -237,11 +235,11 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
'enabled': True,
- 'tenant_id': identity_fakes.PROJECT_2['id'],
+ 'tenant_id': self.fake_project_c.id,
}
# UserManager.create(name, password, email, tenant_id=, enabled=)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
+ user_2.name,
None,
None,
**kwargs
@@ -249,21 +247,21 @@ class TestUserCreate(TestUser):
self.assertEqual(self.columns, columns)
datalist = (
- identity_fakes.user_email,
+ user_2.email,
True,
- identity_fakes.user_id,
- identity_fakes.user_name,
- identity_fakes.PROJECT_2['id'],
+ user_2.id,
+ user_2.name,
+ self.fake_project_c.id,
)
self.assertEqual(datalist, data)
def test_user_create_enable(self):
arglist = [
'--enable',
- identity_fakes.user_name,
+ self.fake_user_c.name,
]
verifylist = [
- ('name', identity_fakes.user_name),
+ ('name', self.fake_user_c.name),
('enable', True),
('disable', False),
]
@@ -281,7 +279,7 @@ class TestUserCreate(TestUser):
}
# UserManager.create(name, password, email, tenant_id=, enabled=)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
+ self.fake_user_c.name,
None,
None,
**kwargs
@@ -293,10 +291,10 @@ class TestUserCreate(TestUser):
def test_user_create_disable(self):
arglist = [
'--disable',
- identity_fakes.user_name,
+ self.fake_user_c.name,
]
verifylist = [
- ('name', identity_fakes.user_name),
+ ('name', self.fake_user_c.name),
('enable', False),
('disable', True),
]
@@ -314,7 +312,7 @@ class TestUserCreate(TestUser):
}
# UserManager.create(name, password, email, tenant_id=, enabled=)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
+ self.fake_user_c.name,
None,
None,
**kwargs
@@ -330,18 +328,14 @@ class TestUserCreate(TestUser):
# need to make this throw an exception...
self.users_mock.create.side_effect = _raise_conflict
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.users_mock.get.return_value = self.fake_user_c
arglist = [
'--or-show',
- identity_fakes.user_name,
+ self.fake_user_c.name,
]
verifylist = [
- ('name', identity_fakes.user_name),
+ ('name', self.fake_user_c.name),
('or_show', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -352,7 +346,7 @@ class TestUserCreate(TestUser):
columns, data = self.cmd.take_action(parsed_args)
# UserManager.create(name, password, email, tenant_id=, enabled=)
- self.users_mock.get.assert_called_with(identity_fakes.user_name)
+ self.users_mock.get.assert_called_with(self.fake_user_c.name)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, data)
@@ -360,10 +354,10 @@ class TestUserCreate(TestUser):
def test_user_create_or_show_not_exists(self):
arglist = [
'--or-show',
- identity_fakes.user_name,
+ self.fake_user_c.name,
]
verifylist = [
- ('name', identity_fakes.user_name),
+ ('name', self.fake_user_c.name),
('or_show', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -380,7 +374,7 @@ class TestUserCreate(TestUser):
}
# UserManager.create(name, password, email, tenant_id=, enabled=)
self.users_mock.create.assert_called_with(
- identity_fakes.user_name,
+ self.fake_user_c.name,
None,
None,
**kwargs
@@ -395,11 +389,7 @@ class TestUserDelete(TestUser):
super(TestUserDelete, self).setUp()
# This is the return value for utils.find_resource()
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.users_mock.get.return_value = self.fake_user
self.users_mock.delete.return_value = None
# Get the command object to test
@@ -407,57 +397,47 @@ class TestUserDelete(TestUser):
def test_user_delete_no_options(self):
arglist = [
- identity_fakes.user_id,
+ self.fake_user.id,
]
verifylist = [
- ('users', [identity_fakes.user_id]),
+ ('users', [self.fake_user.id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.users_mock.delete.assert_called_with(
- identity_fakes.user_id,
+ self.fake_user.id,
)
self.assertIsNone(result)
class TestUserList(TestUser):
+ fake_project_l = identity_fakes.FakeProject.create_one_project()
+ attr = {
+ 'tenantId': fake_project_l.id,
+ }
+ fake_user_l = identity_fakes.FakeUser.create_one_user(attr)
+
columns = (
'ID',
'Name',
)
datalist = (
(
- identity_fakes.user_id,
- identity_fakes.user_name,
+ fake_user_l.id,
+ fake_user_l.name,
),
)
def setUp(self):
super(TestUserList, self).setUp()
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT_2),
- loaded=True,
- )
- self.projects_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- ),
- ]
+ self.projects_mock.get.return_value = self.fake_project_l
+ self.projects_mock.list.return_value = [self.fake_project_l]
- self.users_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- ),
- ]
+ self.users_mock.list.return_value = [self.fake_user_l]
# Get the command object to test
self.cmd = user.ListUser(self.app, None)
@@ -479,13 +459,13 @@ class TestUserList(TestUser):
def test_user_list_project(self):
arglist = [
- '--project', identity_fakes.project_id,
+ '--project', self.fake_project_l.id,
]
verifylist = [
- ('project', identity_fakes.project_id),
+ ('project', self.fake_project_l.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- project_id = identity_fakes.PROJECT_2['id']
+ project_id = self.fake_project_l.id
# In base command class Lister in cliff, abstract method take_action()
# returns a tuple containing the column names and an iterable
@@ -516,10 +496,10 @@ class TestUserList(TestUser):
collist = ('ID', 'Name', 'Project', 'Email', 'Enabled')
self.assertEqual(collist, columns)
datalist = ((
- identity_fakes.user_id,
- identity_fakes.user_name,
- identity_fakes.project_name,
- identity_fakes.user_email,
+ self.fake_user_l.id,
+ self.fake_user_l.name,
+ self.fake_project_l.name,
+ self.fake_user_l.email,
True,
), )
self.assertEqual(datalist, tuple(data))
@@ -530,23 +510,15 @@ class TestUserSet(TestUser):
def setUp(self):
super(TestUserSet, self).setUp()
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.fake_project
+ self.users_mock.get.return_value = self.fake_user
# Get the command object to test
self.cmd = user.SetUser(self.app, None)
def test_user_set_no_options(self):
arglist = [
- identity_fakes.user_name,
+ self.fake_user.name,
]
verifylist = [
('name', None),
@@ -555,7 +527,7 @@ class TestUserSet(TestUser):
('project', None),
('enable', False),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.fake_user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -563,10 +535,31 @@ class TestUserSet(TestUser):
self.assertIsNone(result)
+ def test_user_set_unexist_user(self):
+ arglist = [
+ "unexist-user",
+ ]
+ verifylist = [
+ ('name', None),
+ ('password', None),
+ ('email', None),
+ ('project', None),
+ ('enable', False),
+ ('disable', False),
+ ('user', "unexist-user"),
+ ]
+ self.users_mock.get.side_effect = exceptions.NotFound(None)
+ self.users_mock.find.side_effect = exceptions.NotFound(None)
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(
+ exceptions.CommandError, self.cmd.take_action, parsed_args)
+
def test_user_set_name(self):
arglist = [
'--name', 'qwerty',
- identity_fakes.user_name,
+ self.fake_user.name,
]
verifylist = [
('name', 'qwerty'),
@@ -575,7 +568,7 @@ class TestUserSet(TestUser):
('project', None),
('enable', False),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.fake_user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -588,7 +581,7 @@ class TestUserSet(TestUser):
}
# UserManager.update(user, **kwargs)
self.users_mock.update.assert_called_with(
- identity_fakes.user_id,
+ self.fake_user.id,
**kwargs
)
self.assertIsNone(result)
@@ -596,7 +589,7 @@ class TestUserSet(TestUser):
def test_user_set_password(self):
arglist = [
'--password', 'secret',
- identity_fakes.user_name,
+ self.fake_user.name,
]
verifylist = [
('name', None),
@@ -606,7 +599,7 @@ class TestUserSet(TestUser):
('project', None),
('enable', False),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.fake_user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -614,7 +607,7 @@ class TestUserSet(TestUser):
# UserManager.update_password(user, password)
self.users_mock.update_password.assert_called_with(
- identity_fakes.user_id,
+ self.fake_user.id,
'secret',
)
self.assertIsNone(result)
@@ -622,7 +615,7 @@ class TestUserSet(TestUser):
def test_user_set_password_prompt(self):
arglist = [
'--password-prompt',
- identity_fakes.user_name,
+ self.fake_user.name,
]
verifylist = [
('name', None),
@@ -632,18 +625,18 @@ class TestUserSet(TestUser):
('project', None),
('enable', False),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.fake_user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
mocker = mock.Mock()
mocker.return_value = 'abc123'
- with mock.patch("openstackclient.common.utils.get_password", mocker):
+ with mock.patch("osc_lib.utils.get_password", mocker):
result = self.cmd.take_action(parsed_args)
# UserManager.update_password(user, password)
self.users_mock.update_password.assert_called_with(
- identity_fakes.user_id,
+ self.fake_user.id,
'abc123',
)
self.assertIsNone(result)
@@ -651,7 +644,7 @@ class TestUserSet(TestUser):
def test_user_set_email(self):
arglist = [
'--email', 'barney@example.com',
- identity_fakes.user_name,
+ self.fake_user.name,
]
verifylist = [
('name', None),
@@ -660,7 +653,7 @@ class TestUserSet(TestUser):
('project', None),
('enable', False),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.fake_user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -673,24 +666,24 @@ class TestUserSet(TestUser):
}
# UserManager.update(user, **kwargs)
self.users_mock.update.assert_called_with(
- identity_fakes.user_id,
+ self.fake_user.id,
**kwargs
)
self.assertIsNone(result)
def test_user_set_project(self):
arglist = [
- '--project', identity_fakes.project_id,
- identity_fakes.user_name,
+ '--project', self.fake_project.id,
+ self.fake_user.name,
]
verifylist = [
('name', None),
('password', None),
('email', None),
- ('project', identity_fakes.project_id),
+ ('project', self.fake_project.id),
('enable', False),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.fake_user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -698,15 +691,15 @@ class TestUserSet(TestUser):
# UserManager.update_tenant(user, tenant)
self.users_mock.update_tenant.assert_called_with(
- identity_fakes.user_id,
- identity_fakes.project_id,
+ self.fake_user.id,
+ self.fake_project.id,
)
self.assertIsNone(result)
def test_user_set_enable(self):
arglist = [
'--enable',
- identity_fakes.user_name,
+ self.fake_user.name,
]
verifylist = [
('name', None),
@@ -715,7 +708,7 @@ class TestUserSet(TestUser):
('project', None),
('enable', True),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.fake_user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -727,7 +720,7 @@ class TestUserSet(TestUser):
}
# UserManager.update(user, **kwargs)
self.users_mock.update.assert_called_with(
- identity_fakes.user_id,
+ self.fake_user.id,
**kwargs
)
self.assertIsNone(result)
@@ -735,7 +728,7 @@ class TestUserSet(TestUser):
def test_user_set_disable(self):
arglist = [
'--disable',
- identity_fakes.user_name,
+ self.fake_user.name,
]
verifylist = [
('name', None),
@@ -744,7 +737,7 @@ class TestUserSet(TestUser):
('project', None),
('enable', False),
('disable', True),
- ('user', identity_fakes.user_name),
+ ('user', self.fake_user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -756,7 +749,7 @@ class TestUserSet(TestUser):
}
# UserManager.update(user, **kwargs)
self.users_mock.update.assert_called_with(
- identity_fakes.user_id,
+ self.fake_user.id,
**kwargs
)
self.assertIsNone(result)
@@ -767,21 +760,17 @@ class TestUserShow(TestUser):
def setUp(self):
super(TestUserShow, self).setUp()
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.users_mock.get.return_value = self.fake_user
# Get the command object to test
self.cmd = user.ShowUser(self.app, None)
def test_user_show(self):
arglist = [
- identity_fakes.user_id,
+ self.fake_user.id,
]
verifylist = [
- ('user', identity_fakes.user_id),
+ ('user', self.fake_user.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -790,15 +779,15 @@ class TestUserShow(TestUser):
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
- self.users_mock.get.assert_called_with(identity_fakes.user_id)
+ self.users_mock.get.assert_called_with(self.fake_user.id)
collist = ('email', 'enabled', 'id', 'name', 'project_id')
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.user_email,
+ self.fake_user.email,
True,
- identity_fakes.user_id,
- identity_fakes.user_name,
- identity_fakes.project_id,
+ self.fake_user.id,
+ self.fake_user.name,
+ self.fake_project.id,
)
self.assertEqual(datalist, data)
diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py
index 1422166..c4d24d4 100644
--- a/openstackclient/tests/identity/v3/fakes.py
+++ b/openstackclient/tests/identity/v3/fakes.py
@@ -13,7 +13,12 @@
# under the License.
#
+import copy
import mock
+import uuid
+
+from keystoneauth1 import access
+from keystoneauth1 import fixture
from openstackclient.tests import fakes
from openstackclient.tests import utils
@@ -168,9 +173,17 @@ role_name = 'roller'
ROLE = {
'id': role_id,
'name': role_name,
+ 'domain': None,
'links': base_url + 'roles/' + role_id,
}
+ROLE_2 = {
+ 'id': 'r2',
+ 'name': 'Rolls Royce',
+ 'domain': domain_id,
+ 'links': base_url + 'roles/' + 'r2',
+}
+
service_id = 's-123'
service_name = 'Texaco'
service_type = 'gas'
@@ -193,8 +206,6 @@ SERVICE_WITHOUT_NAME = {
'links': base_url + 'services/' + service_id,
}
-credential_id = 'c-123'
-
endpoint_id = 'e-123'
endpoint_url = 'http://127.0.0.1:35357'
endpoint_region = 'RegionOne'
@@ -355,6 +366,12 @@ ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID = {
'role': {'id': role_id},
}
+ASSIGNMENT_WITH_DOMAIN_ROLE = {
+ 'scope': {'domain': {'id': domain_id}},
+ 'user': {'id': user_id},
+ 'role': {'id': ROLE_2['id']},
+}
+
ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID_INCLUDE_NAMES = {
'scope': {
'domain': {'id': domain_id,
@@ -419,6 +436,36 @@ OAUTH_VERIFIER = {
}
+def fake_auth_ref(fake_token, fake_service=None):
+ """Create an auth_ref using keystoneauth's fixtures"""
+ token_copy = copy.deepcopy(fake_token)
+ token_id = token_copy.pop('id')
+ token = fixture.V3Token(**token_copy)
+ # An auth_ref is actually an access info object
+ auth_ref = access.create(
+ body=token,
+ auth_token=token_id,
+ )
+
+ # Create a service catalog
+ if fake_service:
+ service = token.add_service(
+ fake_service['type'],
+ fake_service['name'],
+ )
+ # TODO(dtroyer): Add an 'id' element to KSA's _Service fixure
+ service['id'] = fake_service['id']
+ for e in fake_service['endpoints']:
+ region = e.get('region_id') or e.get('region', '<none>')
+ service.add_endpoint(
+ e['interface'],
+ e['url'],
+ region=region,
+ )
+
+ return auth_ref
+
+
class FakeAuth(object):
def __init__(self, auth_method_class=None):
@@ -468,6 +515,9 @@ class FakeIdentityv3Client(object):
self.role_assignments.resource_class = fakes.FakeResource(None, {})
self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint']
+ self.auth = FakeAuth()
+ self.auth.client = mock.Mock()
+ self.auth.client.resource_class = fakes.FakeResource(None, {})
class FakeFederationManager(object):
@@ -538,3 +588,327 @@ class TestOAuth1(utils.TestCommand):
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN
)
+
+
+class FakeProject(object):
+ """Fake one or more project."""
+
+ @staticmethod
+ def create_one_project(attrs=None):
+ """Create a fake project.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, name, and so on
+ """
+
+ attrs = attrs or {}
+
+ # set default attributes.
+ project_info = {
+ 'id': 'project-id-' + uuid.uuid4().hex,
+ 'name': 'project-name-' + uuid.uuid4().hex,
+ 'description': 'project-description-' + uuid.uuid4().hex,
+ 'enabled': True,
+ 'is_domain': False,
+ 'domain_id': 'domain-id-' + uuid.uuid4().hex,
+ 'parent_id': 'parent-id-' + uuid.uuid4().hex,
+ 'links': 'links-' + uuid.uuid4().hex,
+ }
+ project_info.update(attrs)
+
+ project = fakes.FakeResource(info=copy.deepcopy(project_info),
+ loaded=True)
+ return project
+
+
+class FakeDomain(object):
+ """Fake one or more domain."""
+
+ @staticmethod
+ def create_one_domain(attrs=None):
+ """Create a fake domain.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, name, and so on
+ """
+
+ attrs = attrs or {}
+
+ # set default attributes.
+ domain_info = {
+ 'id': 'domain-id-' + uuid.uuid4().hex,
+ 'name': 'domain-name-' + uuid.uuid4().hex,
+ 'description': 'domain-description-' + uuid.uuid4().hex,
+ 'enabled': True,
+ 'links': 'links-' + uuid.uuid4().hex,
+ }
+ domain_info.update(attrs)
+
+ domain = fakes.FakeResource(info=copy.deepcopy(domain_info),
+ loaded=True)
+ return domain
+
+
+class FakeCredential(object):
+ """Fake one or more credential."""
+
+ @staticmethod
+ def create_one_credential(attrs=None):
+ """Create a fake credential.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, type, and so on
+ """
+
+ attrs = attrs or {}
+
+ # set default attributes.
+ credential_info = {
+ 'id': 'credential-id-' + uuid.uuid4().hex,
+ 'type': 'cert',
+ 'user_id': 'user-id-' + uuid.uuid4().hex,
+ 'blob': 'credential-data-' + uuid.uuid4().hex,
+ 'project_id': 'project-id-' + uuid.uuid4().hex,
+ 'links': 'links-' + uuid.uuid4().hex,
+ }
+ credential_info.update(attrs)
+
+ credential = fakes.FakeResource(
+ info=copy.deepcopy(credential_info), loaded=True)
+ return credential
+
+ @staticmethod
+ def create_credentials(attrs=None, count=2):
+ """Create multiple fake credentials.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of credentials to fake
+ :return:
+ A list of FakeResource objects faking the credentials
+ """
+ credentials = []
+ for i in range(0, count):
+ credential = FakeCredential.create_one_credential(attrs)
+ credentials.append(credential)
+
+ return credentials
+
+ @staticmethod
+ def get_credentials(credentials=None, count=2):
+ """Get an iterable MagicMock object with a list of faked credentials.
+
+ If credentials list is provided, then initialize the Mock object with
+ the list. Otherwise create one.
+
+ :param List credentials:
+ A list of FakeResource objects faking credentials
+ :param Integer count:
+ The number of credentials to be faked
+ :return
+ An iterable Mock object with side_effect set to a list of faked
+ credentials
+ """
+ if credentials is None:
+ credentials = FakeCredential.create_credentials(count)
+
+ return mock.MagicMock(side_effect=credentials)
+
+
+class FakeUser(object):
+ """Fake one or more user."""
+
+ @staticmethod
+ def create_one_user(attrs=None):
+ """Create a fake user.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, name, and so on
+ """
+
+ attrs = attrs or {}
+
+ # set default attributes.
+ user_info = {
+ 'id': 'user-id-' + uuid.uuid4().hex,
+ 'name': 'user-name-' + uuid.uuid4().hex,
+ 'default_project_id': 'project-' + uuid.uuid4().hex,
+ 'email': 'user-email-' + uuid.uuid4().hex,
+ 'enabled': True,
+ 'domain_id': 'domain-id-' + uuid.uuid4().hex,
+ 'links': 'links-' + uuid.uuid4().hex,
+ }
+ user_info.update(attrs)
+
+ user = fakes.FakeResource(info=copy.deepcopy(user_info),
+ loaded=True)
+ return user
+
+
+class FakeGroup(object):
+ """Fake one or more group."""
+
+ @staticmethod
+ def create_one_group(attrs=None):
+ """Create a fake group.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, name, and so on
+ """
+
+ attrs = attrs or {}
+
+ # set default attributes.
+ group_info = {
+ 'id': 'group-id-' + uuid.uuid4().hex,
+ 'name': 'group-name-' + uuid.uuid4().hex,
+ 'links': 'links-' + uuid.uuid4().hex,
+ 'domain_id': 'domain-id-' + uuid.uuid4().hex,
+ 'description': 'group-description-' + uuid.uuid4().hex,
+ }
+ group_info.update(attrs)
+
+ group = fakes.FakeResource(info=copy.deepcopy(group_info),
+ loaded=True)
+ return group
+
+ @staticmethod
+ def create_groups(attrs=None, count=2):
+ """Create multiple fake groups.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of groups to fake
+ :return:
+ A list of FakeResource objects faking the groups
+ """
+ groups = []
+ for i in range(0, count):
+ group = FakeGroup.create_one_group(attrs)
+ groups.append(group)
+
+ return groups
+
+ @staticmethod
+ def get_groups(groups=None, count=2):
+ """Get an iterable MagicMock object with a list of faked groups.
+
+ If groups list is provided, then initialize the Mock object with
+ the list. Otherwise create one.
+
+ :param List groups:
+ A list of FakeResource objects faking groups
+ :param Integer count:
+ The number of groups to be faked
+ :return
+ An iterable Mock object with side_effect set to a list of faked
+ groups
+ """
+ if groups is None:
+ groups = FakeGroup.create_groups(count)
+
+ return mock.MagicMock(side_effect=groups)
+
+
+class FakeEndpoint(object):
+ """Fake one or more endpoint."""
+
+ @staticmethod
+ def create_one_endpoint(attrs=None):
+ """Create a fake endpoint.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, url, and so on
+ """
+
+ attrs = attrs or {}
+
+ # set default attributes.
+ endpoint_info = {
+ 'id': 'endpoint-id-' + uuid.uuid4().hex,
+ 'url': 'url-' + uuid.uuid4().hex,
+ 'region': 'endpoint-region-' + uuid.uuid4().hex,
+ 'interface': 'admin',
+ 'service_id': 'service-id-' + uuid.uuid4().hex,
+ 'enabled': True,
+ 'links': 'links-' + uuid.uuid4().hex,
+ }
+ endpoint_info.update(attrs)
+
+ endpoint = fakes.FakeResource(info=copy.deepcopy(endpoint_info),
+ loaded=True)
+ return endpoint
+
+
+class FakeService(object):
+ """Fake one or more service."""
+
+ @staticmethod
+ def create_one_service(attrs=None):
+ """Create a fake service.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, name, and so on
+ """
+
+ attrs = attrs or {}
+
+ # set default attributes.
+ service_info = {
+ 'id': 'service-id-' + uuid.uuid4().hex,
+ 'name': 'service-name-' + uuid.uuid4().hex,
+ 'type': 'service-type-' + uuid.uuid4().hex,
+ 'description': 'service-description-' + uuid.uuid4().hex,
+ 'enabled': True,
+ 'links': 'links-' + uuid.uuid4().hex,
+ }
+ service_info.update(attrs)
+
+ service = fakes.FakeResource(info=copy.deepcopy(service_info),
+ loaded=True)
+ return service
+
+
+class FakeRoleAssignment(object):
+ """Fake one or more role assignment."""
+
+ @staticmethod
+ def create_one_role_assignment(attrs=None):
+ """Create a fake role assignment.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with scope, user, and so on
+ """
+
+ attrs = attrs or {}
+
+ # set default attributes.
+ role_assignment_info = {
+ 'scope': {'project': {'id': 'project-id-' + uuid.uuid4().hex}},
+ 'user': {'id': 'user-id-' + uuid.uuid4().hex},
+ 'role': {'id': 'role-id-' + uuid.uuid4().hex},
+ }
+ role_assignment_info.update(attrs)
+
+ role_assignment = fakes.FakeResource(
+ info=copy.deepcopy(role_assignment_info), loaded=True)
+
+ return role_assignment
diff --git a/openstackclient/tests/identity/v3/test_catalog.py b/openstackclient/tests/identity/v3/test_catalog.py
index 1b8fa08..e3c5ed3 100644
--- a/openstackclient/tests/identity/v3/test_catalog.py
+++ b/openstackclient/tests/identity/v3/test_catalog.py
@@ -14,6 +14,7 @@
import mock
from openstackclient.identity.v3 import catalog
+from openstackclient.tests.identity.v3 import fakes as identity_fakes
from openstackclient.tests import utils
@@ -50,7 +51,7 @@ class TestCatalog(utils.TestCommand):
super(TestCatalog, self).setUp()
self.sc_mock = mock.MagicMock()
- self.sc_mock.service_catalog.get_data.return_value = [
+ self.sc_mock.service_catalog.catalog.return_value = [
self.fake_service,
]
@@ -69,6 +70,13 @@ class TestCatalogList(TestCatalog):
self.cmd = catalog.ListCatalog(self.app, None)
def test_catalog_list(self):
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.TOKEN_WITH_PROJECT_ID,
+ fake_service=self.fake_service,
+ )
+ self.ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = self.ar_mock
+
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -77,7 +85,6 @@ class TestCatalogList(TestCatalog):
# returns a tuple containing the column names and an iterable
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- self.sc_mock.service_catalog.get_data.assert_called_with()
collist = ('Name', 'Type', 'Endpoints')
self.assertEqual(collist, columns)
@@ -101,6 +108,13 @@ class TestCatalogShow(TestCatalog):
self.cmd = catalog.ShowCatalog(self.app, None)
def test_catalog_show(self):
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.TOKEN_WITH_PROJECT_ID,
+ fake_service=self.fake_service,
+ )
+ self.ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = self.ar_mock
+
arglist = [
'compute',
]
@@ -113,7 +127,6 @@ class TestCatalogShow(TestCatalog):
# returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
- self.sc_mock.service_catalog.get_data.assert_called_with()
collist = ('endpoints', 'id', 'name', 'type')
self.assertEqual(collist, columns)
diff --git a/openstackclient/tests/identity/v3/test_consumer.py b/openstackclient/tests/identity/v3/test_consumer.py
index 4a8cf08..d90c734 100644
--- a/openstackclient/tests/identity/v3/test_consumer.py
+++ b/openstackclient/tests/identity/v3/test_consumer.py
@@ -83,7 +83,7 @@ class TestConsumerDelete(TestOAuth1):
identity_fakes.consumer_id,
]
verifylist = [
- ('consumer', identity_fakes.consumer_id),
+ ('consumer', [identity_fakes.consumer_id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
diff --git a/openstackclient/tests/identity/v3/test_credential.py b/openstackclient/tests/identity/v3/test_credential.py
index d886612..b272087 100644
--- a/openstackclient/tests/identity/v3/test_credential.py
+++ b/openstackclient/tests/identity/v3/test_credential.py
@@ -10,7 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import json
+import mock
+from mock import call
+
+from osc_lib import exceptions
from openstackclient.identity.v3 import credential
from openstackclient.tests.identity.v3 import fakes as identity_fakes
@@ -18,16 +21,6 @@ from openstackclient.tests import utils
class TestCredential(identity_fakes.TestIdentityv3):
- data = {
- "access": "abc123",
- "secret": "hidden-message",
- "trust_id": None
- }
-
- def __init__(self, *args):
- super(TestCredential, self).__init__(*args)
-
- self.json_data = json.dumps(self.data)
def setUp(self):
super(TestCredential, self).setUp()
@@ -45,15 +38,221 @@ class TestCredential(identity_fakes.TestIdentityv3):
self.projects_mock.reset_mock()
+class TestCredentialCreate(TestCredential):
+
+ user = identity_fakes.FakeUser.create_one_user()
+ project = identity_fakes.FakeProject.create_one_project()
+ columns = (
+ 'blob',
+ 'id',
+ 'project_id',
+ 'type',
+ 'user_id',
+ )
+
+ def setUp(self):
+ super(TestCredentialCreate, self).setUp()
+
+ self.credential = identity_fakes.FakeCredential.create_one_credential(
+ attrs={'user_id': self.user.id, 'project_id': self.project.id})
+ self.credentials_mock.create.return_value = self.credential
+ self.users_mock.get.return_value = self.user
+ self.projects_mock.get.return_value = self.project
+ self.data = (
+ self.credential.blob,
+ self.credential.id,
+ self.credential.project_id,
+ self.credential.type,
+ self.credential.user_id,
+ )
+
+ self.cmd = credential.CreateCredential(self.app, None)
+
+ def test_credential_create_no_options(self):
+ arglist = [
+ self.credential.user_id,
+ self.credential.blob,
+ ]
+ verifylist = [
+ ('user', self.credential.user_id),
+ ('data', self.credential.blob),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'user': self.credential.user_id,
+ 'type': self.credential.type,
+ 'blob': self.credential.blob,
+ 'project': None,
+ }
+ self.credentials_mock.create.assert_called_once_with(
+ **kwargs
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_credential_create_with_options(self):
+ arglist = [
+ self.credential.user_id,
+ self.credential.blob,
+ '--type', self.credential.type,
+ '--project', self.credential.project_id,
+ ]
+ verifylist = [
+ ('user', self.credential.user_id),
+ ('data', self.credential.blob),
+ ('type', self.credential.type),
+ ('project', self.credential.project_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'user': self.credential.user_id,
+ 'type': self.credential.type,
+ 'blob': self.credential.blob,
+ 'project': self.credential.project_id,
+ }
+ self.credentials_mock.create.assert_called_once_with(
+ **kwargs
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_credential_create_with_invalid_type(self):
+ arglist = [
+ self.credential.user_id,
+ self.credential.blob,
+ '--type', 'invalid_type',
+ ]
+ verifylist = [
+ ('user', self.credential.user_id),
+ ('data', self.credential.blob),
+ ('type', 'invalid_type'),
+ ]
+ self.assertRaises(utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+
+class TestCredentialDelete(TestCredential):
+
+ credentials = identity_fakes.FakeCredential.create_credentials(count=2)
+
+ def setUp(self):
+ super(TestCredentialDelete, self).setUp()
+
+ self.credentials_mock.delete.return_value = None
+
+ # Get the command object to test
+ self.cmd = credential.DeleteCredential(self.app, None)
+
+ def test_credential_delete(self):
+ arglist = [
+ self.credentials[0].id,
+ ]
+ verifylist = [
+ ('credential', [self.credentials[0].id]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.credentials_mock.delete.assert_called_with(
+ self.credentials[0].id,
+ )
+ self.assertIsNone(result)
+
+ def test_credential_multi_delete(self):
+ arglist = []
+ for c in self.credentials:
+ arglist.append(c.id)
+ verifylist = [
+ ('credential', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for c in self.credentials:
+ calls.append(call(c.id))
+ self.credentials_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_credential_multi_delete_with_exception(self):
+ arglist = [
+ self.credentials[0].id,
+ 'unexist_credential',
+ ]
+ verifylist = [
+ ('credential', [self.credentials[0].id, 'unexist_credential'])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ delete_mock_result = [None, exceptions.CommandError]
+ self.credentials_mock.delete = (
+ mock.MagicMock(side_effect=delete_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 credential failed to delete.', str(e))
+
+ self.credentials_mock.delete.assert_any_call(self.credentials[0].id)
+ self.credentials_mock.delete.assert_any_call('unexist_credential')
+
+
+class TestCredentialList(TestCredential):
+
+ credential = identity_fakes.FakeCredential.create_one_credential()
+
+ columns = ('ID', 'Type', 'User ID', 'Data', 'Project ID')
+ data = ((
+ credential.id,
+ credential.type,
+ credential.user_id,
+ credential.blob,
+ credential.project_id,
+ ), )
+
+ def setUp(self):
+ super(TestCredentialList, self).setUp()
+
+ self.credentials_mock.list.return_value = [self.credential]
+
+ # Get the command object to test
+ self.cmd = credential.ListCredential(self.app, None)
+
+ def test_domain_list_no_options(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.credentials_mock.list.assert_called_with()
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, tuple(data))
+
+
class TestCredentialSet(TestCredential):
+ credential = identity_fakes.FakeCredential.create_one_credential()
+
def setUp(self):
super(TestCredentialSet, self).setUp()
self.cmd = credential.SetCredential(self.app, None)
def test_credential_set_no_options(self):
arglist = [
- identity_fakes.credential_id,
+ self.credential.id,
]
self.assertRaises(utils.ParserException,
@@ -62,8 +261,8 @@ class TestCredentialSet(TestCredential):
def test_credential_set_missing_user(self):
arglist = [
'--type', 'ec2',
- '--data', self.json_data,
- identity_fakes.credential_id,
+ '--data', self.credential.blob,
+ self.credential.id,
]
self.assertRaises(utils.ParserException,
@@ -71,9 +270,9 @@ class TestCredentialSet(TestCredential):
def test_credential_set_missing_type(self):
arglist = [
- '--user', identity_fakes.user_name,
- '--data', self.json_data,
- identity_fakes.credential_id,
+ '--user', self.credential.user_id,
+ '--data', self.credential.blob,
+ self.credential.id,
]
self.assertRaises(utils.ParserException,
@@ -81,9 +280,9 @@ class TestCredentialSet(TestCredential):
def test_credential_set_missing_data(self):
arglist = [
- '--user', identity_fakes.user_name,
+ '--user', self.credential.user_id,
'--type', 'ec2',
- identity_fakes.credential_id,
+ self.credential.id,
]
self.assertRaises(utils.ParserException,
@@ -91,10 +290,10 @@ class TestCredentialSet(TestCredential):
def test_credential_set_valid(self):
arglist = [
- '--user', identity_fakes.user_name,
+ '--user', self.credential.user_id,
'--type', 'ec2',
- '--data', self.json_data,
- identity_fakes.credential_id,
+ '--data', self.credential.blob,
+ self.credential.id,
]
parsed_args = self.check_parser(self.cmd, arglist, [])
@@ -104,14 +303,55 @@ class TestCredentialSet(TestCredential):
def test_credential_set_valid_with_project(self):
arglist = [
- '--user', identity_fakes.user_name,
+ '--user', self.credential.user_id,
'--type', 'ec2',
- '--data', self.json_data,
- '--project', identity_fakes.project_name,
- identity_fakes.credential_id,
+ '--data', self.credential.blob,
+ '--project', self.credential.project_id,
+ self.credential.id,
]
parsed_args = self.check_parser(self.cmd, arglist, [])
result = self.cmd.take_action(parsed_args)
self.assertIsNone(result)
+
+
+class TestCredentialShow(TestCredential):
+
+ columns = (
+ 'blob',
+ 'id',
+ 'project_id',
+ 'type',
+ 'user_id',
+ )
+
+ def setUp(self):
+ super(TestCredentialShow, self).setUp()
+
+ self.credential = identity_fakes.FakeCredential.create_one_credential()
+ self.credentials_mock.get.return_value = self.credential
+ self.data = (
+ self.credential.blob,
+ self.credential.id,
+ self.credential.project_id,
+ self.credential.type,
+ self.credential.user_id,
+ )
+
+ self.cmd = credential.ShowCredential(self.app, None)
+
+ def test_credential_show(self):
+ arglist = [
+ self.credential.id,
+ ]
+ verifylist = [
+ ('credential', self.credential.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.credentials_mock.get.assert_called_once_with(self.credential.id)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/identity/v3/test_domain.py
index f3777f1..5e09402 100644
--- a/openstackclient/tests/identity/v3/test_domain.py
+++ b/openstackclient/tests/identity/v3/test_domain.py
@@ -10,10 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import copy
-
from openstackclient.identity.v3 import domain
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
@@ -35,20 +32,17 @@ class TestDomainCreate(TestDomain):
'id',
'name',
)
- datalist = (
- identity_fakes.domain_description,
- True,
- identity_fakes.domain_id,
- identity_fakes.domain_name,
- )
def setUp(self):
super(TestDomainCreate, self).setUp()
- self.domains_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
+ self.domain = identity_fakes.FakeDomain.create_one_domain()
+ self.domains_mock.create.return_value = self.domain
+ self.datalist = (
+ self.domain.description,
+ True,
+ self.domain.id,
+ self.domain.name,
)
# Get the command object to test
@@ -56,10 +50,10 @@ class TestDomainCreate(TestDomain):
def test_domain_create_no_options(self):
arglist = [
- identity_fakes.domain_name,
+ self.domain.name,
]
verifylist = [
- ('name', identity_fakes.domain_name),
+ ('name', self.domain.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -70,7 +64,7 @@ class TestDomainCreate(TestDomain):
# Set expected values
kwargs = {
- 'name': identity_fakes.domain_name,
+ 'name': self.domain.name,
'description': None,
'enabled': True,
}
@@ -84,11 +78,11 @@ class TestDomainCreate(TestDomain):
def test_domain_create_description(self):
arglist = [
'--description', 'new desc',
- identity_fakes.domain_name,
+ self.domain.name,
]
verifylist = [
('description', 'new desc'),
- ('name', identity_fakes.domain_name),
+ ('name', self.domain.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -99,7 +93,7 @@ class TestDomainCreate(TestDomain):
# Set expected values
kwargs = {
- 'name': identity_fakes.domain_name,
+ 'name': self.domain.name,
'description': 'new desc',
'enabled': True,
}
@@ -113,11 +107,11 @@ class TestDomainCreate(TestDomain):
def test_domain_create_enable(self):
arglist = [
'--enable',
- identity_fakes.domain_name,
+ self.domain.name,
]
verifylist = [
('enable', True),
- ('name', identity_fakes.domain_name),
+ ('name', self.domain.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -128,7 +122,7 @@ class TestDomainCreate(TestDomain):
# Set expected values
kwargs = {
- 'name': identity_fakes.domain_name,
+ 'name': self.domain.name,
'description': None,
'enabled': True,
}
@@ -142,11 +136,11 @@ class TestDomainCreate(TestDomain):
def test_domain_create_disable(self):
arglist = [
'--disable',
- identity_fakes.domain_name,
+ self.domain.name,
]
verifylist = [
('disable', True),
- ('name', identity_fakes.domain_name),
+ ('name', self.domain.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -157,7 +151,7 @@ class TestDomainCreate(TestDomain):
# Set expected values
kwargs = {
- 'name': identity_fakes.domain_name,
+ 'name': self.domain.name,
'description': None,
'enabled': False,
}
@@ -171,15 +165,13 @@ class TestDomainCreate(TestDomain):
class TestDomainDelete(TestDomain):
+ domain = identity_fakes.FakeDomain.create_one_domain()
+
def setUp(self):
super(TestDomainDelete, self).setUp()
# This is the return value for utils.find_resource()
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
+ self.domains_mock.get.return_value = self.domain
self.domains_mock.delete.return_value = None
# Get the command object to test
@@ -187,33 +179,29 @@ class TestDomainDelete(TestDomain):
def test_domain_delete(self):
arglist = [
- identity_fakes.domain_id,
+ self.domain.id,
]
verifylist = [
- ('domain', identity_fakes.domain_id),
+ ('domain', [self.domain.id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.domains_mock.delete.assert_called_with(
- identity_fakes.domain_id,
+ self.domain.id,
)
self.assertIsNone(result)
class TestDomainList(TestDomain):
+ domain = identity_fakes.FakeDomain.create_one_domain()
+
def setUp(self):
super(TestDomainList, self).setUp()
- self.domains_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- ),
- ]
+ self.domains_mock.list.return_value = [self.domain]
# Get the command object to test
self.cmd = domain.ListDomain(self.app, None)
@@ -232,56 +220,54 @@ class TestDomainList(TestDomain):
collist = ('ID', 'Name', 'Enabled', 'Description')
self.assertEqual(collist, columns)
datalist = ((
- identity_fakes.domain_id,
- identity_fakes.domain_name,
+ self.domain.id,
+ self.domain.name,
True,
- identity_fakes.domain_description,
+ self.domain.description,
), )
self.assertEqual(datalist, tuple(data))
class TestDomainSet(TestDomain):
+ domain = identity_fakes.FakeDomain.create_one_domain()
+
def setUp(self):
super(TestDomainSet, self).setUp()
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
+ self.domains_mock.get.return_value = self.domain
- self.domains_mock.update.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
+ self.domains_mock.update.return_value = self.domain
# Get the command object to test
self.cmd = domain.SetDomain(self.app, None)
def test_domain_set_no_options(self):
arglist = [
- identity_fakes.domain_name,
+ self.domain.name,
]
verifylist = [
- ('domain', identity_fakes.domain_name),
+ ('domain', self.domain.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.assertNotCalled(self.domains_mock.update)
+ kwargs = {}
+ self.domains_mock.update.assert_called_with(
+ self.domain.id,
+ **kwargs
+ )
self.assertIsNone(result)
def test_domain_set_name(self):
arglist = [
'--name', 'qwerty',
- identity_fakes.domain_id,
+ self.domain.id,
]
verifylist = [
('name', 'qwerty'),
- ('domain', identity_fakes.domain_id),
+ ('domain', self.domain.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -292,7 +278,7 @@ class TestDomainSet(TestDomain):
'name': 'qwerty',
}
self.domains_mock.update.assert_called_with(
- identity_fakes.domain_id,
+ self.domain.id,
**kwargs
)
self.assertIsNone(result)
@@ -300,11 +286,11 @@ class TestDomainSet(TestDomain):
def test_domain_set_description(self):
arglist = [
'--description', 'new desc',
- identity_fakes.domain_id,
+ self.domain.id,
]
verifylist = [
('description', 'new desc'),
- ('domain', identity_fakes.domain_id),
+ ('domain', self.domain.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -315,7 +301,7 @@ class TestDomainSet(TestDomain):
'description': 'new desc',
}
self.domains_mock.update.assert_called_with(
- identity_fakes.domain_id,
+ self.domain.id,
**kwargs
)
self.assertIsNone(result)
@@ -323,11 +309,11 @@ class TestDomainSet(TestDomain):
def test_domain_set_enable(self):
arglist = [
'--enable',
- identity_fakes.domain_id,
+ self.domain.id,
]
verifylist = [
('enable', True),
- ('domain', identity_fakes.domain_id),
+ ('domain', self.domain.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -338,7 +324,7 @@ class TestDomainSet(TestDomain):
'enabled': True,
}
self.domains_mock.update.assert_called_with(
- identity_fakes.domain_id,
+ self.domain.id,
**kwargs
)
self.assertIsNone(result)
@@ -346,11 +332,11 @@ class TestDomainSet(TestDomain):
def test_domain_set_disable(self):
arglist = [
'--disable',
- identity_fakes.domain_id,
+ self.domain.id,
]
verifylist = [
('disable', True),
- ('domain', identity_fakes.domain_id),
+ ('domain', self.domain.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -361,7 +347,7 @@ class TestDomainSet(TestDomain):
'enabled': False,
}
self.domains_mock.update.assert_called_with(
- identity_fakes.domain_id,
+ self.domain.id,
**kwargs
)
self.assertIsNone(result)
@@ -372,38 +358,44 @@ class TestDomainShow(TestDomain):
def setUp(self):
super(TestDomainShow, self).setUp()
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
-
+ self.domain = identity_fakes.FakeDomain.create_one_domain()
+ self.domains_mock.get.return_value = self.domain
# Get the command object to test
self.cmd = domain.ShowDomain(self.app, None)
def test_domain_show(self):
arglist = [
- identity_fakes.domain_id,
+ self.domain.id,
]
verifylist = [
- ('domain', identity_fakes.domain_id),
+ ('domain', self.domain.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.identity.tokens.get_token_data.return_value = \
+ {'token':
+ {'project':
+ {'domain':
+ {'id': 'd1',
+ 'name': 'd1'
+ }
+ }
+ }
+ }
# In base command class ShowOne in cliff, abstract method take_action()
# returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
self.domains_mock.get.assert_called_with(
- identity_fakes.domain_id,
+ self.domain.id,
)
collist = ('description', 'enabled', 'id', 'name')
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.domain_description,
+ self.domain.description,
True,
- identity_fakes.domain_id,
- identity_fakes.domain_name,
+ self.domain.id,
+ self.domain.name,
)
self.assertEqual(datalist, data)
diff --git a/openstackclient/tests/identity/v3/test_endpoint.py b/openstackclient/tests/identity/v3/test_endpoint.py
index d953459..b2463a0 100644
--- a/openstackclient/tests/identity/v3/test_endpoint.py
+++ b/openstackclient/tests/identity/v3/test_endpoint.py
@@ -10,11 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-
-import copy
-
from openstackclient.identity.v3 import endpoint
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
@@ -31,12 +27,11 @@ class TestEndpoint(identity_fakes.TestIdentityv3):
self.services_mock = self.app.client_manager.identity.services
self.services_mock.reset_mock()
- def get_fake_service_name(self):
- return identity_fakes.service_name
-
class TestEndpointCreate(TestEndpoint):
+ service = identity_fakes.FakeService.create_one_service()
+
columns = (
'enabled',
'id',
@@ -51,33 +46,27 @@ class TestEndpointCreate(TestEndpoint):
def setUp(self):
super(TestEndpointCreate, self).setUp()
- self.endpoints_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ENDPOINT),
- loaded=True,
- )
+ self.endpoint = identity_fakes.FakeEndpoint.create_one_endpoint(
+ attrs={'service_id': self.service.id})
+ self.endpoints_mock.create.return_value = self.endpoint
# This is the return value for common.find_resource(service)
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
+ self.services_mock.get.return_value = self.service
# Get the command object to test
self.cmd = endpoint.CreateEndpoint(self.app, None)
def test_endpoint_create_no_options(self):
arglist = [
- identity_fakes.service_id,
- identity_fakes.endpoint_interface,
- identity_fakes.endpoint_url,
+ self.service.id,
+ self.endpoint.interface,
+ self.endpoint.url,
]
verifylist = [
('enabled', True),
- ('service', identity_fakes.service_id),
- ('interface', identity_fakes.endpoint_interface),
- ('url', identity_fakes.endpoint_url),
+ ('service', self.service.id),
+ ('interface', self.endpoint.interface),
+ ('url', self.endpoint.url),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -88,9 +77,9 @@ class TestEndpointCreate(TestEndpoint):
# Set expected values
kwargs = {
- 'service': identity_fakes.service_id,
- 'url': identity_fakes.endpoint_url,
- 'interface': identity_fakes.endpoint_interface,
+ 'service': self.service.id,
+ 'url': self.endpoint.url,
+ 'interface': self.endpoint.interface,
'enabled': True,
'region': None,
}
@@ -102,29 +91,29 @@ class TestEndpointCreate(TestEndpoint):
self.assertEqual(self.columns, columns)
datalist = (
True,
- identity_fakes.endpoint_id,
- identity_fakes.endpoint_interface,
- identity_fakes.endpoint_region,
- identity_fakes.service_id,
- self.get_fake_service_name(),
- identity_fakes.service_type,
- identity_fakes.endpoint_url,
+ self.endpoint.id,
+ self.endpoint.interface,
+ self.endpoint.region,
+ self.service.id,
+ self.service.name,
+ self.service.type,
+ self.endpoint.url,
)
self.assertEqual(datalist, data)
def test_endpoint_create_region(self):
arglist = [
- identity_fakes.service_id,
- identity_fakes.endpoint_interface,
- identity_fakes.endpoint_url,
- '--region', identity_fakes.endpoint_region,
+ self.service.id,
+ self.endpoint.interface,
+ self.endpoint.url,
+ '--region', self.endpoint.region,
]
verifylist = [
('enabled', True),
- ('service', identity_fakes.service_id),
- ('interface', identity_fakes.endpoint_interface),
- ('url', identity_fakes.endpoint_url),
- ('region', identity_fakes.endpoint_region),
+ ('service', self.service.id),
+ ('interface', self.endpoint.interface),
+ ('url', self.endpoint.url),
+ ('region', self.endpoint.region),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -135,11 +124,11 @@ class TestEndpointCreate(TestEndpoint):
# Set expected values
kwargs = {
- 'service': identity_fakes.service_id,
- 'url': identity_fakes.endpoint_url,
- 'interface': identity_fakes.endpoint_interface,
+ 'service': self.service.id,
+ 'url': self.endpoint.url,
+ 'interface': self.endpoint.interface,
'enabled': True,
- 'region': identity_fakes.endpoint_region,
+ 'region': self.endpoint.region,
}
self.endpoints_mock.create.assert_called_with(
@@ -149,28 +138,28 @@ class TestEndpointCreate(TestEndpoint):
self.assertEqual(self.columns, columns)
datalist = (
True,
- identity_fakes.endpoint_id,
- identity_fakes.endpoint_interface,
- identity_fakes.endpoint_region,
- identity_fakes.service_id,
- self.get_fake_service_name(),
- identity_fakes.service_type,
- identity_fakes.endpoint_url,
+ self.endpoint.id,
+ self.endpoint.interface,
+ self.endpoint.region,
+ self.service.id,
+ self.service.name,
+ self.service.type,
+ self.endpoint.url,
)
self.assertEqual(datalist, data)
def test_endpoint_create_enable(self):
arglist = [
- identity_fakes.service_id,
- identity_fakes.endpoint_interface,
- identity_fakes.endpoint_url,
+ self.service.id,
+ self.endpoint.interface,
+ self.endpoint.url,
'--enable'
]
verifylist = [
('enabled', True),
- ('service', identity_fakes.service_id),
- ('interface', identity_fakes.endpoint_interface),
- ('url', identity_fakes.endpoint_url),
+ ('service', self.service.id),
+ ('interface', self.endpoint.interface),
+ ('url', self.endpoint.url),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -181,9 +170,9 @@ class TestEndpointCreate(TestEndpoint):
# Set expected values
kwargs = {
- 'service': identity_fakes.service_id,
- 'url': identity_fakes.endpoint_url,
- 'interface': identity_fakes.endpoint_interface,
+ 'service': self.service.id,
+ 'url': self.endpoint.url,
+ 'interface': self.endpoint.interface,
'enabled': True,
'region': None,
}
@@ -195,28 +184,28 @@ class TestEndpointCreate(TestEndpoint):
self.assertEqual(self.columns, columns)
datalist = (
True,
- identity_fakes.endpoint_id,
- identity_fakes.endpoint_interface,
- identity_fakes.endpoint_region,
- identity_fakes.service_id,
- self.get_fake_service_name(),
- identity_fakes.service_type,
- identity_fakes.endpoint_url,
+ self.endpoint.id,
+ self.endpoint.interface,
+ self.endpoint.region,
+ self.service.id,
+ self.service.name,
+ self.service.type,
+ self.endpoint.url,
)
self.assertEqual(datalist, data)
def test_endpoint_create_disable(self):
arglist = [
- identity_fakes.service_id,
- identity_fakes.endpoint_interface,
- identity_fakes.endpoint_url,
+ self.service.id,
+ self.endpoint.interface,
+ self.endpoint.url,
'--disable',
]
verifylist = [
('enabled', False),
- ('service', identity_fakes.service_id),
- ('interface', identity_fakes.endpoint_interface),
- ('url', identity_fakes.endpoint_url),
+ ('service', self.service.id),
+ ('interface', self.endpoint.interface),
+ ('url', self.endpoint.url),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -227,9 +216,9 @@ class TestEndpointCreate(TestEndpoint):
# Set expected values
kwargs = {
- 'service': identity_fakes.service_id,
- 'url': identity_fakes.endpoint_url,
- 'interface': identity_fakes.endpoint_interface,
+ 'service': self.service.id,
+ 'url': self.endpoint.url,
+ 'interface': self.endpoint.interface,
'enabled': False,
'region': None,
}
@@ -241,28 +230,26 @@ class TestEndpointCreate(TestEndpoint):
self.assertEqual(self.columns, columns)
datalist = (
True,
- identity_fakes.endpoint_id,
- identity_fakes.endpoint_interface,
- identity_fakes.endpoint_region,
- identity_fakes.service_id,
- self.get_fake_service_name(),
- identity_fakes.service_type,
- identity_fakes.endpoint_url,
+ self.endpoint.id,
+ self.endpoint.interface,
+ self.endpoint.region,
+ self.service.id,
+ self.service.name,
+ self.service.type,
+ self.endpoint.url,
)
self.assertEqual(datalist, data)
class TestEndpointDelete(TestEndpoint):
+ endpoint = identity_fakes.FakeEndpoint.create_one_endpoint()
+
def setUp(self):
super(TestEndpointDelete, self).setUp()
# This is the return value for utils.find_resource(endpoint)
- self.endpoints_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ENDPOINT),
- loaded=True,
- )
+ self.endpoints_mock.get.return_value = self.endpoint
self.endpoints_mock.delete.return_value = None
# Get the command object to test
@@ -270,23 +257,27 @@ class TestEndpointDelete(TestEndpoint):
def test_endpoint_delete(self):
arglist = [
- identity_fakes.endpoint_id,
+ self.endpoint.id,
]
verifylist = [
- ('endpoint', identity_fakes.endpoint_id),
+ ('endpoint', [self.endpoint.id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.endpoints_mock.delete.assert_called_with(
- identity_fakes.endpoint_id,
+ self.endpoint.id,
)
self.assertIsNone(result)
class TestEndpointList(TestEndpoint):
+ service = identity_fakes.FakeService.create_one_service()
+ endpoint = identity_fakes.FakeEndpoint.create_one_endpoint(
+ attrs={'service_id': service.id})
+
columns = (
'ID',
'Region',
@@ -300,20 +291,10 @@ class TestEndpointList(TestEndpoint):
def setUp(self):
super(TestEndpointList, self).setUp()
- self.endpoints_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ENDPOINT),
- loaded=True,
- ),
- ]
+ self.endpoints_mock.list.return_value = [self.endpoint]
# This is the return value for common.find_resource(service)
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
+ self.services_mock.get.return_value = self.service
# Get the command object to test
self.cmd = endpoint.ListEndpoint(self.app, None)
@@ -332,23 +313,23 @@ class TestEndpointList(TestEndpoint):
self.assertEqual(self.columns, columns)
datalist = (
(
- identity_fakes.endpoint_id,
- identity_fakes.endpoint_region,
- self.get_fake_service_name(),
- identity_fakes.service_type,
+ self.endpoint.id,
+ self.endpoint.region,
+ self.service.name,
+ self.service.type,
True,
- identity_fakes.endpoint_interface,
- identity_fakes.endpoint_url,
+ self.endpoint.interface,
+ self.endpoint.url,
),
)
self.assertEqual(datalist, tuple(data))
def test_endpoint_list_service(self):
arglist = [
- '--service', identity_fakes.service_id,
+ '--service', self.service.id,
]
verifylist = [
- ('service', identity_fakes.service_id),
+ ('service', self.service.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -359,30 +340,30 @@ class TestEndpointList(TestEndpoint):
# Set expected values
kwargs = {
- 'service': identity_fakes.service_id,
+ 'service': self.service.id,
}
self.endpoints_mock.list.assert_called_with(**kwargs)
self.assertEqual(self.columns, columns)
datalist = (
(
- identity_fakes.endpoint_id,
- identity_fakes.endpoint_region,
- self.get_fake_service_name(),
- identity_fakes.service_type,
+ self.endpoint.id,
+ self.endpoint.region,
+ self.service.name,
+ self.service.type,
True,
- identity_fakes.endpoint_interface,
- identity_fakes.endpoint_url,
+ self.endpoint.interface,
+ self.endpoint.url,
),
)
self.assertEqual(datalist, tuple(data))
def test_endpoint_list_interface(self):
arglist = [
- '--interface', identity_fakes.endpoint_interface,
+ '--interface', self.endpoint.interface,
]
verifylist = [
- ('interface', identity_fakes.endpoint_interface),
+ ('interface', self.endpoint.interface),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -393,30 +374,30 @@ class TestEndpointList(TestEndpoint):
# Set expected values
kwargs = {
- 'interface': identity_fakes.endpoint_interface,
+ 'interface': self.endpoint.interface,
}
self.endpoints_mock.list.assert_called_with(**kwargs)
self.assertEqual(self.columns, columns)
datalist = (
(
- identity_fakes.endpoint_id,
- identity_fakes.endpoint_region,
- self.get_fake_service_name(),
- identity_fakes.service_type,
+ self.endpoint.id,
+ self.endpoint.region,
+ self.service.name,
+ self.service.type,
True,
- identity_fakes.endpoint_interface,
- identity_fakes.endpoint_url,
+ self.endpoint.interface,
+ self.endpoint.url,
),
)
self.assertEqual(datalist, tuple(data))
def test_endpoint_list_region(self):
arglist = [
- '--region', identity_fakes.endpoint_region,
+ '--region', self.endpoint.region,
]
verifylist = [
- ('region', identity_fakes.endpoint_region),
+ ('region', self.endpoint.region),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -427,20 +408,20 @@ class TestEndpointList(TestEndpoint):
# Set expected values
kwargs = {
- 'region': identity_fakes.endpoint_region,
+ 'region': self.endpoint.region,
}
self.endpoints_mock.list.assert_called_with(**kwargs)
self.assertEqual(self.columns, columns)
datalist = (
(
- identity_fakes.endpoint_id,
- identity_fakes.endpoint_region,
- self.get_fake_service_name(),
- identity_fakes.service_type,
+ self.endpoint.id,
+ self.endpoint.region,
+ self.service.name,
+ self.service.type,
True,
- identity_fakes.endpoint_interface,
- identity_fakes.endpoint_url,
+ self.endpoint.interface,
+ self.endpoint.url,
),
)
self.assertEqual(datalist, tuple(data))
@@ -448,54 +429,56 @@ class TestEndpointList(TestEndpoint):
class TestEndpointSet(TestEndpoint):
+ service = identity_fakes.FakeService.create_one_service()
+ endpoint = identity_fakes.FakeEndpoint.create_one_endpoint(
+ attrs={'service_id': service.id})
+
def setUp(self):
super(TestEndpointSet, self).setUp()
# This is the return value for utils.find_resource(endpoint)
- self.endpoints_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ENDPOINT),
- loaded=True,
- )
+ self.endpoints_mock.get.return_value = self.endpoint
- self.endpoints_mock.update.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ENDPOINT),
- loaded=True,
- )
+ self.endpoints_mock.update.return_value = self.endpoint
# This is the return value for common.find_resource(service)
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
+ self.services_mock.get.return_value = self.service
# Get the command object to test
self.cmd = endpoint.SetEndpoint(self.app, None)
def test_endpoint_set_no_options(self):
arglist = [
- identity_fakes.endpoint_id,
+ self.endpoint.id,
]
verifylist = [
- ('endpoint', identity_fakes.endpoint_id),
+ ('endpoint', self.endpoint.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.assertNotCalled(self.endpoints_mock.update)
+ kwargs = {
+ 'enabled': None,
+ 'interface': None,
+ 'region': None,
+ 'service': None,
+ 'url': None,
+ }
+ self.endpoints_mock.update.assert_called_with(
+ self.endpoint.id,
+ **kwargs
+ )
self.assertIsNone(result)
def test_endpoint_set_interface(self):
arglist = [
'--interface', 'public',
- identity_fakes.endpoint_id
+ self.endpoint.id
]
verifylist = [
('interface', 'public'),
- ('endpoint', identity_fakes.endpoint_id),
+ ('endpoint', self.endpoint.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -510,7 +493,7 @@ class TestEndpointSet(TestEndpoint):
'service': None,
}
self.endpoints_mock.update.assert_called_with(
- identity_fakes.endpoint_id,
+ self.endpoint.id,
**kwargs
)
self.assertIsNone(result)
@@ -518,11 +501,11 @@ class TestEndpointSet(TestEndpoint):
def test_endpoint_set_url(self):
arglist = [
'--url', 'http://localhost:5000',
- identity_fakes.endpoint_id
+ self.endpoint.id
]
verifylist = [
('url', 'http://localhost:5000'),
- ('endpoint', identity_fakes.endpoint_id),
+ ('endpoint', self.endpoint.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -537,19 +520,19 @@ class TestEndpointSet(TestEndpoint):
'service': None,
}
self.endpoints_mock.update.assert_called_with(
- identity_fakes.endpoint_id,
+ self.endpoint.id,
**kwargs
)
self.assertIsNone(result)
def test_endpoint_set_service(self):
arglist = [
- '--service', identity_fakes.service_id,
- identity_fakes.endpoint_id
+ '--service', self.service.id,
+ self.endpoint.id
]
verifylist = [
- ('service', identity_fakes.service_id),
- ('endpoint', identity_fakes.endpoint_id),
+ ('service', self.service.id),
+ ('endpoint', self.endpoint.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -561,10 +544,10 @@ class TestEndpointSet(TestEndpoint):
'interface': None,
'url': None,
'region': None,
- 'service': identity_fakes.service_id,
+ 'service': self.service.id,
}
self.endpoints_mock.update.assert_called_with(
- identity_fakes.endpoint_id,
+ self.endpoint.id,
**kwargs
)
self.assertIsNone(result)
@@ -572,11 +555,11 @@ class TestEndpointSet(TestEndpoint):
def test_endpoint_set_region(self):
arglist = [
'--region', 'e-rzzz',
- identity_fakes.endpoint_id
+ self.endpoint.id
]
verifylist = [
('region', 'e-rzzz'),
- ('endpoint', identity_fakes.endpoint_id),
+ ('endpoint', self.endpoint.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -591,7 +574,7 @@ class TestEndpointSet(TestEndpoint):
'service': None,
}
self.endpoints_mock.update.assert_called_with(
- identity_fakes.endpoint_id,
+ self.endpoint.id,
**kwargs
)
self.assertIsNone(result)
@@ -599,11 +582,11 @@ class TestEndpointSet(TestEndpoint):
def test_endpoint_set_enable(self):
arglist = [
'--enable',
- identity_fakes.endpoint_id
+ self.endpoint.id
]
verifylist = [
('enabled', True),
- ('endpoint', identity_fakes.endpoint_id),
+ ('endpoint', self.endpoint.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -618,7 +601,7 @@ class TestEndpointSet(TestEndpoint):
'service': None,
}
self.endpoints_mock.update.assert_called_with(
- identity_fakes.endpoint_id,
+ self.endpoint.id,
**kwargs
)
self.assertIsNone(result)
@@ -626,11 +609,11 @@ class TestEndpointSet(TestEndpoint):
def test_endpoint_set_disable(self):
arglist = [
'--disable',
- identity_fakes.endpoint_id
+ self.endpoint.id
]
verifylist = [
('disabled', True),
- ('endpoint', identity_fakes.endpoint_id),
+ ('endpoint', self.endpoint.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -645,7 +628,7 @@ class TestEndpointSet(TestEndpoint):
'service': None,
}
self.endpoints_mock.update.assert_called_with(
- identity_fakes.endpoint_id,
+ self.endpoint.id,
**kwargs
)
self.assertIsNone(result)
@@ -653,31 +636,27 @@ class TestEndpointSet(TestEndpoint):
class TestEndpointShow(TestEndpoint):
+ service = identity_fakes.FakeService.create_one_service()
+ endpoint = identity_fakes.FakeEndpoint.create_one_endpoint(
+ attrs={'service_id': service.id})
+
def setUp(self):
super(TestEndpointShow, self).setUp()
- self.endpoints_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ENDPOINT),
- loaded=True,
- )
+ self.endpoints_mock.get.return_value = self.endpoint
# This is the return value for common.find_resource(service)
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
+ self.services_mock.get.return_value = self.service
# Get the command object to test
self.cmd = endpoint.ShowEndpoint(self.app, None)
def test_endpoint_show(self):
arglist = [
- identity_fakes.endpoint_id,
+ self.endpoint.id,
]
verifylist = [
- ('endpoint', identity_fakes.endpoint_id),
+ ('endpoint', self.endpoint.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -686,7 +665,7 @@ class TestEndpointShow(TestEndpoint):
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
self.endpoints_mock.get.assert_called_with(
- identity_fakes.endpoint_id,
+ self.endpoint.id,
)
collist = (
@@ -702,89 +681,70 @@ class TestEndpointShow(TestEndpoint):
self.assertEqual(collist, columns)
datalist = (
True,
- identity_fakes.endpoint_id,
- identity_fakes.endpoint_interface,
- identity_fakes.endpoint_region,
- identity_fakes.service_id,
- self.get_fake_service_name(),
- identity_fakes.service_type,
- identity_fakes.endpoint_url,
+ self.endpoint.id,
+ self.endpoint.interface,
+ self.endpoint.region,
+ self.service.id,
+ self.service.name,
+ self.service.type,
+ self.endpoint.url,
)
self.assertEqual(datalist, data)
class TestEndpointCreateServiceWithoutName(TestEndpointCreate):
+ service = identity_fakes.FakeService.create_one_service(
+ attrs={'service_name': ''})
+
def setUp(self):
super(TestEndpointCreate, self).setUp()
- self.endpoints_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ENDPOINT),
- loaded=True,
- )
+ self.endpoint = identity_fakes.FakeEndpoint.create_one_endpoint(
+ attrs={'service_id': self.service.id})
+
+ self.endpoints_mock.create.return_value = self.endpoint
# This is the return value for common.find_resource(service)
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE_WITHOUT_NAME),
- loaded=True,
- )
+ self.services_mock.get.return_value = self.service
# Get the command object to test
self.cmd = endpoint.CreateEndpoint(self.app, None)
- def get_fake_service_name(self):
- return ''
-
class TestEndpointListServiceWithoutName(TestEndpointList):
+ service = identity_fakes.FakeService.create_one_service(
+ attrs={'service_name': ''})
+ endpoint = identity_fakes.FakeEndpoint.create_one_endpoint(
+ attrs={'service_id': service.id})
+
def setUp(self):
super(TestEndpointList, self).setUp()
- self.endpoints_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ENDPOINT),
- loaded=True,
- ),
- ]
+ self.endpoints_mock.list.return_value = [self.endpoint]
# This is the return value for common.find_resource(service)
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE_WITHOUT_NAME),
- loaded=True,
- )
+ self.services_mock.get.return_value = self.service
# Get the command object to test
self.cmd = endpoint.ListEndpoint(self.app, None)
- def get_fake_service_name(self):
- return ''
-
class TestEndpointShowServiceWithoutName(TestEndpointShow):
+ service = identity_fakes.FakeService.create_one_service(
+ attrs={'service_name': ''})
+ endpoint = identity_fakes.FakeEndpoint.create_one_endpoint(
+ attrs={'service_id': service.id})
+
def setUp(self):
super(TestEndpointShow, self).setUp()
- self.endpoints_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.ENDPOINT),
- loaded=True,
- )
+ self.endpoints_mock.get.return_value = self.endpoint
# This is the return value for common.find_resource(service)
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE_WITHOUT_NAME),
- loaded=True,
- )
+ self.services_mock.get.return_value = self.service
# Get the command object to test
self.cmd = endpoint.ShowEndpoint(self.app, None)
-
- def get_fake_service_name(self):
- return ''
diff --git a/openstackclient/tests/identity/v3/test_group.py b/openstackclient/tests/identity/v3/test_group.py
index c5f5dbc..a678dee 100644
--- a/openstackclient/tests/identity/v3/test_group.py
+++ b/openstackclient/tests/identity/v3/test_group.py
@@ -11,10 +11,13 @@
# under the License.
#
-import copy
+import mock
+from mock import call
+
+from keystoneauth1 import exceptions as ks_exc
+from osc_lib import exceptions
from openstackclient.identity.v3 import group
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
@@ -36,46 +39,251 @@ class TestGroup(identity_fakes.TestIdentityv3):
self.users_mock.reset_mock()
+class TestGroupAddUser(TestGroup):
+
+ group = identity_fakes.FakeGroup.create_one_group()
+ user = identity_fakes.FakeUser.create_one_user()
+
+ def setUp(self):
+ super(TestGroupAddUser, self).setUp()
+
+ self.groups_mock.get.return_value = self.group
+ self.users_mock.get.return_value = self.user
+ self.users_mock.add_to_group.return_value = None
+
+ self.cmd = group.AddUserToGroup(self.app, None)
+
+ def test_group_add_user(self):
+ arglist = [
+ self.group.name,
+ self.user.name,
+ ]
+ verifylist = [
+ ('group', self.group.name),
+ ('user', self.user.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.users_mock.add_to_group.assert_called_once_with(
+ self.user.id, self.group.id)
+ self.assertIsNone(result)
+
+
+class TestGroupCheckUser(TestGroup):
+
+ group = identity_fakes.FakeGroup.create_one_group()
+ user = identity_fakes.FakeUser.create_one_user()
+
+ def setUp(self):
+ super(TestGroupCheckUser, self).setUp()
+
+ self.groups_mock.get.return_value = self.group
+ self.users_mock.get.return_value = self.user
+ self.users_mock.check_in_group.return_value = None
+
+ self.cmd = group.CheckUserInGroup(self.app, None)
+
+ def test_group_check_user(self):
+ arglist = [
+ self.group.name,
+ self.user.name,
+ ]
+ verifylist = [
+ ('group', self.group.name),
+ ('user', self.user.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.users_mock.check_in_group.assert_called_once_with(
+ self.user.id, self.group.id)
+ self.assertIsNone(result)
+
+
+class TestGroupCreate(TestGroup):
+
+ domain = identity_fakes.FakeDomain.create_one_domain()
+
+ columns = (
+ 'description',
+ 'domain_id',
+ 'id',
+ 'name',
+ )
+
+ def setUp(self):
+ super(TestGroupCreate, self).setUp()
+ self.group = identity_fakes.FakeGroup.create_one_group(
+ attrs={'domain_id': self.domain.id})
+ self.data = (
+ self.group.description,
+ self.group.domain_id,
+ self.group.id,
+ self.group.name,
+ )
+
+ self.groups_mock.create.return_value = self.group
+ self.groups_mock.get.return_value = self.group
+ self.domains_mock.get.return_value = self.domain
+
+ self.cmd = group.CreateGroup(self.app, None)
+
+ def test_group_create(self):
+ arglist = [
+ self.group.name,
+ ]
+ verifylist = [
+ ('name', self.group.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.groups_mock.create.assert_called_once_with(
+ name=self.group.name,
+ domain=None,
+ description=None,
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_group_create_with_options(self):
+ arglist = [
+ '--domain', self.domain.name,
+ '--description', self.group.description,
+ self.group.name,
+ ]
+ verifylist = [
+ ('domain', self.domain.name),
+ ('description', self.group.description),
+ ('name', self.group.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.groups_mock.create.assert_called_once_with(
+ name=self.group.name,
+ domain=self.domain.id,
+ description=self.group.description,
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_group_create_or_show(self):
+ self.groups_mock.create.side_effect = ks_exc.Conflict()
+ arglist = [
+ '--or-show',
+ self.group.name,
+ ]
+ verifylist = [
+ ('or_show', True),
+ ('name', self.group.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.groups_mock.get.assert_called_once_with(self.group.name)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+
+class TestGroupDelete(TestGroup):
+
+ domain = identity_fakes.FakeDomain.create_one_domain()
+ groups = identity_fakes.FakeGroup.create_groups(
+ attrs={'domain_id': domain.id}, count=2)
+
+ def setUp(self):
+ super(TestGroupDelete, self).setUp()
+
+ self.groups_mock.get = (
+ identity_fakes.FakeGroup.get_groups(self.groups))
+ self.groups_mock.delete.return_value = None
+ self.domains_mock.get.return_value = self.domain
+
+ self.cmd = group.DeleteGroup(self.app, None)
+
+ def test_group_delete(self):
+ arglist = [
+ self.groups[0].id,
+ ]
+ verifylist = [
+ ('groups', [self.groups[0].id]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.groups_mock.get.assert_called_once_with(self.groups[0].id)
+ self.groups_mock.delete.assert_called_once_with(self.groups[0].id)
+ self.assertIsNone(result)
+
+ def test_group_multi_delete(self):
+ arglist = []
+ verifylist = []
+
+ for g in self.groups:
+ arglist.append(g.id)
+ verifylist = [
+ ('groups', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for g in self.groups:
+ calls.append(call(g.id))
+ self.groups_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_group_delete_with_domain(self):
+ get_mock_result = [exceptions.CommandError, self.groups[0]]
+ self.groups_mock.get = (
+ mock.MagicMock(side_effect=get_mock_result))
+
+ arglist = [
+ '--domain', self.domain.id,
+ self.groups[0].id,
+ ]
+ verifylist = [
+ ('domain', self.groups[0].domain_id),
+ ('groups', [self.groups[0].id]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.groups_mock.get.assert_any_call(
+ self.groups[0].id, domain_id=self.domain.id)
+ self.groups_mock.delete.assert_called_once_with(self.groups[0].id)
+ self.assertIsNone(result)
+
+
class TestGroupList(TestGroup):
+ domain = identity_fakes.FakeDomain.create_one_domain()
+ group = identity_fakes.FakeGroup.create_one_group()
+ user = identity_fakes.FakeUser.create_one_user()
+
columns = (
'ID',
'Name',
)
datalist = (
(
- identity_fakes.group_id,
- identity_fakes.group_name,
+ group.id,
+ group.name,
),
)
def setUp(self):
super(TestGroupList, self).setUp()
- self.groups_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.GROUP),
- loaded=True,
- )
- self.groups_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.GROUP),
- loaded=True,
- ),
- ]
-
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
+ self.groups_mock.get.return_value = self.group
+ self.groups_mock.list.return_value = [self.group]
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.domains_mock.get.return_value = self.domain
+
+ self.users_mock.get.return_value = self.user
# Get the command object to test
self.cmd = group.ListGroup(self.app, None)
@@ -105,10 +313,10 @@ class TestGroupList(TestGroup):
def test_group_list_domain(self):
arglist = [
- '--domain', identity_fakes.domain_id,
+ '--domain', self.domain.id,
]
verifylist = [
- ('domain', identity_fakes.domain_id),
+ ('domain', self.domain.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -119,7 +327,7 @@ class TestGroupList(TestGroup):
# Set expected values
kwargs = {
- 'domain': identity_fakes.domain_id,
+ 'domain': self.domain.id,
'user': None,
}
@@ -132,10 +340,10 @@ class TestGroupList(TestGroup):
def test_group_list_user(self):
arglist = [
- '--user', identity_fakes.user_name,
+ '--user', self.user.name,
]
verifylist = [
- ('user', identity_fakes.user_name),
+ ('user', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -147,7 +355,7 @@ class TestGroupList(TestGroup):
# Set expected values
kwargs = {
'domain': None,
- 'user': identity_fakes.user_id,
+ 'user': self.user.id,
}
self.groups_mock.list.assert_called_with(
@@ -186,10 +394,176 @@ class TestGroupList(TestGroup):
'Description',
)
datalist = ((
- identity_fakes.group_id,
- identity_fakes.group_name,
- '',
- '',
+ self.group.id,
+ self.group.name,
+ self.group.domain_id,
+ self.group.description,
), )
self.assertEqual(columns, columns)
self.assertEqual(datalist, tuple(data))
+
+
+class TestGroupRemoveUser(TestGroup):
+
+ group = identity_fakes.FakeGroup.create_one_group()
+ user = identity_fakes.FakeUser.create_one_user()
+
+ def setUp(self):
+ super(TestGroupRemoveUser, self).setUp()
+
+ self.groups_mock.get.return_value = self.group
+ self.users_mock.get.return_value = self.user
+ self.users_mock.remove_from_group.return_value = None
+
+ self.cmd = group.RemoveUserFromGroup(self.app, None)
+
+ def test_group_remove_user(self):
+ arglist = [
+ self.group.id,
+ self.user.id,
+ ]
+ verifylist = [
+ ('group', self.group.id),
+ ('user', self.user.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.users_mock.remove_from_group.assert_called_once_with(
+ self.user.id, self.group.id)
+ self.assertIsNone(result)
+
+
+class TestGroupSet(TestGroup):
+
+ domain = identity_fakes.FakeDomain.create_one_domain()
+ group = identity_fakes.FakeGroup.create_one_group(
+ attrs={'domain_id': domain.id})
+
+ def setUp(self):
+ super(TestGroupSet, self).setUp()
+
+ self.groups_mock.get.return_value = self.group
+ self.domains_mock.get.return_value = self.domain
+ self.groups_mock.update.return_value = None
+
+ self.cmd = group.SetGroup(self.app, None)
+
+ def test_group_set_nothing(self):
+ arglist = [
+ self.group.id,
+ ]
+ verifylist = [
+ ('group', self.group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.groups_mock.update.assert_called_once_with(self.group.id)
+ self.assertIsNone(result)
+
+ def test_group_set_name_and_description(self):
+ arglist = [
+ '--name', 'new_name',
+ '--description', 'new_description',
+ self.group.id,
+ ]
+ verifylist = [
+ ('name', 'new_name'),
+ ('description', 'new_description'),
+ ('group', self.group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ kwargs = {
+ 'name': 'new_name',
+ 'description': 'new_description',
+ }
+ self.groups_mock.update.assert_called_once_with(
+ self.group.id, **kwargs)
+ self.assertIsNone(result)
+
+ def test_group_set_with_domain(self):
+ get_mock_result = [exceptions.CommandError, self.group]
+ self.groups_mock.get = (
+ mock.MagicMock(side_effect=get_mock_result))
+
+ arglist = [
+ '--domain', self.domain.id,
+ self.group.id,
+ ]
+ verifylist = [
+ ('domain', self.domain.id),
+ ('group', self.group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.groups_mock.get.assert_any_call(
+ self.group.id, domain_id=self.domain.id)
+ self.groups_mock.update.assert_called_once_with(self.group.id)
+ self.assertIsNone(result)
+
+
+class TestGroupShow(TestGroup):
+
+ domain = identity_fakes.FakeDomain.create_one_domain()
+
+ columns = (
+ 'description',
+ 'domain_id',
+ 'id',
+ 'name',
+ )
+
+ def setUp(self):
+ super(TestGroupShow, self).setUp()
+ self.group = identity_fakes.FakeGroup.create_one_group(
+ attrs={'domain_id': self.domain.id})
+ self.data = (
+ self.group.description,
+ self.group.domain_id,
+ self.group.id,
+ self.group.name,
+ )
+
+ self.groups_mock.get.return_value = self.group
+ self.domains_mock.get.return_value = self.domain
+
+ self.cmd = group.ShowGroup(self.app, None)
+
+ def test_group_show(self):
+ arglist = [
+ self.group.id,
+ ]
+ verifylist = [
+ ('group', self.group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.groups_mock.get.assert_called_once_with(self.group.id)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_group_show_with_domain(self):
+ get_mock_result = [exceptions.CommandError, self.group]
+ self.groups_mock.get = (
+ mock.MagicMock(side_effect=get_mock_result))
+
+ arglist = [
+ '--domain', self.domain.id,
+ self.group.id,
+ ]
+ verifylist = [
+ ('domain', self.domain.id),
+ ('group', self.group.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.groups_mock.get.assert_any_call(
+ self.group.id, domain_id=self.domain.id)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py
index 3ff7981..d86ac11 100644
--- a/openstackclient/tests/identity/v3/test_identity_provider.py
+++ b/openstackclient/tests/identity/v3/test_identity_provider.py
@@ -13,7 +13,6 @@
# under the License.
import copy
-
import mock
from openstackclient.identity.v3 import identity_provider
@@ -255,7 +254,7 @@ class TestIdentityProviderDelete(TestIdentityProvider):
identity_fakes.idp_id,
]
verifylist = [
- ('identity_provider', identity_fakes.idp_id),
+ ('identity_provider', [identity_fakes.idp_id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -357,19 +356,11 @@ class TestIdentityProviderSet(TestIdentityProvider):
('remote_id', None)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- columns, data = self.cmd.take_action(parsed_args)
+ self.cmd.take_action(parsed_args)
self.identity_providers_mock.update.assert_called_with(
identity_fakes.idp_id,
description=new_description,
)
- self.assertEqual(self.columns, columns)
- datalist = (
- identity_fakes.idp_description,
- False,
- identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
- )
- self.assertEqual(datalist, data)
def test_identity_provider_disable(self):
"""Disable Identity Provider
@@ -403,22 +394,13 @@ class TestIdentityProviderSet(TestIdentityProvider):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- columns, data = self.cmd.take_action(parsed_args)
+ self.cmd.take_action(parsed_args)
self.identity_providers_mock.update.assert_called_with(
identity_fakes.idp_id,
enabled=False,
remote_ids=identity_fakes.idp_remote_ids
)
- self.assertEqual(self.columns, columns)
- datalist = (
- identity_fakes.idp_description,
- False,
- identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
- )
- self.assertEqual(datalist, data)
-
def test_identity_provider_enable(self):
"""Enable Identity Provider.
@@ -449,12 +431,10 @@ class TestIdentityProviderSet(TestIdentityProvider):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- columns, data = self.cmd.take_action(parsed_args)
+ self.cmd.take_action(parsed_args)
self.identity_providers_mock.update.assert_called_with(
identity_fakes.idp_id, enabled=True,
remote_ids=identity_fakes.idp_remote_ids)
- self.assertEqual(self.columns, columns)
- self.assertEqual(self.datalist, data)
def test_identity_provider_replace_remote_ids(self):
"""Enable Identity Provider.
@@ -489,18 +469,10 @@ class TestIdentityProviderSet(TestIdentityProvider):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- columns, data = self.cmd.take_action(parsed_args)
+ self.cmd.take_action(parsed_args)
self.identity_providers_mock.update.assert_called_with(
identity_fakes.idp_id, enabled=True,
remote_ids=[self.new_remote_id])
- self.assertEqual(self.columns, columns)
- datalist = (
- identity_fakes.idp_description,
- True,
- identity_fakes.idp_id,
- [self.new_remote_id]
- )
- self.assertEqual(datalist, data)
def test_identity_provider_replace_remote_ids_file(self):
"""Enable Identity Provider.
@@ -539,18 +511,10 @@ class TestIdentityProviderSet(TestIdentityProvider):
mocker.return_value = self.new_remote_id
with mock.patch("openstackclient.identity.v3.identity_provider."
"utils.read_blob_file_contents", mocker):
- columns, data = self.cmd.take_action(parsed_args)
+ self.cmd.take_action(parsed_args)
self.identity_providers_mock.update.assert_called_with(
identity_fakes.idp_id, enabled=True,
remote_ids=[self.new_remote_id])
- self.assertEqual(self.columns, columns)
- datalist = (
- identity_fakes.idp_description,
- True,
- identity_fakes.idp_id,
- [self.new_remote_id]
- )
- self.assertEqual(datalist, data)
def test_identity_provider_no_options(self):
def prepare(self):
@@ -581,12 +545,7 @@ class TestIdentityProviderSet(TestIdentityProvider):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- columns, data = self.cmd.take_action(parsed_args)
-
- # expect take_action() to return (None, None) as
- # neither --enable nor --disable was specified
- self.assertIsNone(columns)
- self.assertIsNone(data)
+ self.cmd.take_action(parsed_args)
class TestIdentityProviderShow(TestIdentityProvider):
@@ -599,7 +558,11 @@ class TestIdentityProviderShow(TestIdentityProvider):
copy.deepcopy(identity_fakes.IDENTITY_PROVIDER),
loaded=True,
)
+
+ self.identity_providers_mock.get.side_effect = [Exception("Not found"),
+ ret]
self.identity_providers_mock.get.return_value = ret
+
# Get the command object to test
self.cmd = identity_provider.ShowIdentityProvider(self.app, None)
diff --git a/openstackclient/tests/identity/v3/test_mappings.py b/openstackclient/tests/identity/v3/test_mappings.py
index b9e3b1d..09a383e 100644
--- a/openstackclient/tests/identity/v3/test_mappings.py
+++ b/openstackclient/tests/identity/v3/test_mappings.py
@@ -13,10 +13,10 @@
# under the License.
import copy
-
import mock
-from openstackclient.common import exceptions
+from osc_lib import exceptions
+
from openstackclient.identity.v3 import mapping
from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
@@ -90,7 +90,7 @@ class TestMappingDelete(TestMapping):
identity_fakes.mapping_id
]
verifylist = [
- ('mapping', identity_fakes.mapping_id)
+ ('mapping', [identity_fakes.mapping_id])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py
index 1e9d1c8..65874ba 100644
--- a/openstackclient/tests/identity/v3/test_project.py
+++ b/openstackclient/tests/identity/v3/test_project.py
@@ -13,12 +13,11 @@
# under the License.
#
-import copy
import mock
-from openstackclient.common import exceptions
+from osc_lib import exceptions
+
from openstackclient.identity.v3 import project
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
@@ -38,48 +37,46 @@ class TestProject(identity_fakes.TestIdentityv3):
class TestProjectCreate(TestProject):
+ domain = identity_fakes.FakeDomain.create_one_domain()
+
columns = (
'description',
'domain_id',
'enabled',
'id',
- 'name'
- )
- datalist = (
- identity_fakes.project_description,
- identity_fakes.domain_id,
- True,
- identity_fakes.project_id,
- identity_fakes.project_name,
+ 'is_domain',
+ 'name',
+ 'parent_id',
)
def setUp(self):
super(TestProjectCreate, self).setUp()
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
-
- self.projects_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
+ self.project = identity_fakes.FakeProject.create_one_project(
+ attrs={'domain_id': self.domain.id})
+ self.domains_mock.get.return_value = self.domain
+ self.projects_mock.create.return_value = self.project
+ self.datalist = (
+ self.project.description,
+ self.project.domain_id,
+ True,
+ self.project.id,
+ False,
+ self.project.name,
+ self.project.parent_id,
)
-
# Get the command object to test
self.cmd = project.CreateProject(self.app, None)
def test_project_create_no_options(self):
arglist = [
- identity_fakes.project_name,
+ self.project.name,
]
verifylist = [
('parent', None),
('enable', False),
('disable', False),
- ('name', identity_fakes.project_name),
+ ('name', self.project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -90,7 +87,7 @@ class TestProjectCreate(TestProject):
# Set expected values
kwargs = {
- 'name': identity_fakes.project_name,
+ 'name': self.project.name,
'domain': None,
'description': None,
'enabled': True,
@@ -102,27 +99,37 @@ class TestProjectCreate(TestProject):
**kwargs
)
- collist = ('description', 'domain_id', 'enabled', 'id', 'name')
+ collist = (
+ 'description',
+ 'domain_id',
+ 'enabled',
+ 'id',
+ 'is_domain',
+ 'name',
+ 'parent_id',
+ )
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.project_description,
- identity_fakes.domain_id,
+ self.project.description,
+ self.project.domain_id,
True,
- identity_fakes.project_id,
- identity_fakes.project_name,
+ self.project.id,
+ False,
+ self.project.name,
+ self.project.parent_id,
)
self.assertEqual(datalist, data)
def test_project_create_description(self):
arglist = [
'--description', 'new desc',
- identity_fakes.project_name,
+ self.project.name,
]
verifylist = [
('description', 'new desc'),
('enable', False),
('disable', False),
- ('name', identity_fakes.project_name),
+ ('name', self.project.name),
('parent', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -134,7 +141,7 @@ class TestProjectCreate(TestProject):
# Set expected values
kwargs = {
- 'name': identity_fakes.project_name,
+ 'name': self.project.name,
'domain': None,
'description': 'new desc',
'enabled': True,
@@ -151,14 +158,14 @@ class TestProjectCreate(TestProject):
def test_project_create_domain(self):
arglist = [
- '--domain', identity_fakes.domain_name,
- identity_fakes.project_name,
+ '--domain', self.project.domain_id,
+ self.project.name,
]
verifylist = [
- ('domain', identity_fakes.domain_name),
+ ('domain', self.project.domain_id),
('enable', False),
('disable', False),
- ('name', identity_fakes.project_name),
+ ('name', self.project.name),
('parent', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -170,8 +177,8 @@ class TestProjectCreate(TestProject):
# Set expected values
kwargs = {
- 'name': identity_fakes.project_name,
- 'domain': identity_fakes.domain_id,
+ 'name': self.project.name,
+ 'domain': self.project.domain_id,
'description': None,
'enabled': True,
'parent': None,
@@ -187,27 +194,27 @@ class TestProjectCreate(TestProject):
def test_project_create_domain_no_perms(self):
arglist = [
- '--domain', identity_fakes.domain_id,
- identity_fakes.project_name,
+ '--domain', self.project.domain_id,
+ self.project.name,
]
verifylist = [
- ('domain', identity_fakes.domain_id),
+ ('domain', self.project.domain_id),
('enable', False),
('disable', False),
- ('name', identity_fakes.project_name),
+ ('name', self.project.name),
('parent', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
mocker = mock.Mock()
mocker.return_value = None
- with mock.patch("openstackclient.common.utils.find_resource", mocker):
+ with mock.patch("osc_lib.utils.find_resource", mocker):
columns, data = self.cmd.take_action(parsed_args)
# Set expected values
kwargs = {
- 'name': identity_fakes.project_name,
- 'domain': identity_fakes.domain_id,
+ 'name': self.project.name,
+ 'domain': self.project.domain_id,
'description': None,
'enabled': True,
'parent': None,
@@ -221,12 +228,12 @@ class TestProjectCreate(TestProject):
def test_project_create_enable(self):
arglist = [
'--enable',
- identity_fakes.project_name,
+ self.project.name,
]
verifylist = [
('enable', True),
('disable', False),
- ('name', identity_fakes.project_name),
+ ('name', self.project.name),
('parent', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -238,7 +245,7 @@ class TestProjectCreate(TestProject):
# Set expected values
kwargs = {
- 'name': identity_fakes.project_name,
+ 'name': self.project.name,
'domain': None,
'description': None,
'enabled': True,
@@ -256,12 +263,12 @@ class TestProjectCreate(TestProject):
def test_project_create_disable(self):
arglist = [
'--disable',
- identity_fakes.project_name,
+ self.project.name,
]
verifylist = [
('enable', False),
('disable', True),
- ('name', identity_fakes.project_name),
+ ('name', self.project.name),
('parent', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -273,7 +280,7 @@ class TestProjectCreate(TestProject):
# Set expected values
kwargs = {
- 'name': identity_fakes.project_name,
+ 'name': self.project.name,
'domain': None,
'description': None,
'enabled': False,
@@ -292,11 +299,11 @@ class TestProjectCreate(TestProject):
arglist = [
'--property', 'fee=fi',
'--property', 'fo=fum',
- identity_fakes.project_name,
+ self.project.name,
]
verifylist = [
('property', {'fee': 'fi', 'fo': 'fum'}),
- ('name', identity_fakes.project_name),
+ ('name', self.project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -307,7 +314,7 @@ class TestProjectCreate(TestProject):
# Set expected values
kwargs = {
- 'name': identity_fakes.project_name,
+ 'name': self.project.name,
'domain': None,
'description': None,
'enabled': True,
@@ -325,37 +332,32 @@ class TestProjectCreate(TestProject):
self.assertEqual(self.datalist, data)
def test_project_create_parent(self):
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
- self.projects_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT),
- loaded=True,
- )
+ self.parent = identity_fakes.FakeProject.create_one_project()
+ self.project = identity_fakes.FakeProject.create_one_project(
+ attrs={'domain_id': self.domain.id, 'parent_id': self.parent.id})
+ self.projects_mock.get.return_value = self.parent
+ self.projects_mock.create.return_value = self.project
arglist = [
- '--domain', identity_fakes.PROJECT_WITH_PARENT['domain_id'],
- '--parent', identity_fakes.PROJECT['name'],
- identity_fakes.PROJECT_WITH_PARENT['name'],
+ '--domain', self.project.domain_id,
+ '--parent', self.parent.name,
+ self.project.name,
]
verifylist = [
- ('domain', identity_fakes.PROJECT_WITH_PARENT['domain_id']),
- ('parent', identity_fakes.PROJECT['name']),
+ ('domain', self.project.domain_id),
+ ('parent', self.parent.name),
('enable', False),
('disable', False),
- ('name', identity_fakes.PROJECT_WITH_PARENT['name']),
+ ('name', self.project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
kwargs = {
- 'name': identity_fakes.PROJECT_WITH_PARENT['name'],
- 'domain': identity_fakes.PROJECT_WITH_PARENT['domain_id'],
- 'parent': identity_fakes.PROJECT['id'],
+ 'name': self.project.name,
+ 'domain': self.project.domain_id,
+ 'parent': self.parent.id,
'description': None,
'enabled': True,
}
@@ -369,17 +371,19 @@ class TestProjectCreate(TestProject):
'domain_id',
'enabled',
'id',
+ 'is_domain',
'name',
'parent_id',
)
self.assertEqual(columns, collist)
datalist = (
- identity_fakes.PROJECT_WITH_PARENT['description'],
- identity_fakes.PROJECT_WITH_PARENT['domain_id'],
- identity_fakes.PROJECT_WITH_PARENT['enabled'],
- identity_fakes.PROJECT_WITH_PARENT['id'],
- identity_fakes.PROJECT_WITH_PARENT['name'],
- identity_fakes.PROJECT['id'],
+ self.project.description,
+ self.project.domain_id,
+ self.project.enabled,
+ self.project.id,
+ self.project.is_domain,
+ self.project.name,
+ self.parent.id,
)
self.assertEqual(data, datalist)
@@ -391,16 +395,16 @@ class TestProjectCreate(TestProject):
'Invalid parent')
arglist = [
- '--domain', identity_fakes.domain_name,
+ '--domain', self.project.domain_id,
'--parent', 'invalid',
- identity_fakes.project_name,
+ self.project.name,
]
verifylist = [
- ('domain', identity_fakes.domain_name),
+ ('domain', self.project.domain_id),
('parent', 'invalid'),
('enable', False),
('disable', False),
- ('name', identity_fakes.project_name),
+ ('name', self.project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -413,15 +417,13 @@ class TestProjectCreate(TestProject):
class TestProjectDelete(TestProject):
+ project = identity_fakes.FakeProject.create_one_project()
+
def setUp(self):
super(TestProjectDelete, self).setUp()
# This is the return value for utils.find_resource()
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
self.projects_mock.delete.return_value = None
# Get the command object to test
@@ -429,44 +431,42 @@ class TestProjectDelete(TestProject):
def test_project_delete_no_options(self):
arglist = [
- identity_fakes.project_id,
+ self.project.id,
]
verifylist = [
- ('projects', [identity_fakes.project_id]),
+ ('projects', [self.project.id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.projects_mock.delete.assert_called_with(
- identity_fakes.project_id,
+ self.project.id,
)
self.assertIsNone(result)
class TestProjectList(TestProject):
+ domain = identity_fakes.FakeDomain.create_one_domain()
+ project = identity_fakes.FakeProject.create_one_project(
+ attrs={'domain_id': domain.id})
+
columns = (
'ID',
'Name',
)
datalist = (
(
- identity_fakes.project_id,
- identity_fakes.project_name,
+ project.id,
+ project.name,
),
)
def setUp(self):
super(TestProjectList, self).setUp()
- self.projects_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- ),
- ]
+ self.projects_mock.list.return_value = [self.project]
# Get the command object to test
self.cmd = project.ListProject(self.app, None)
@@ -503,27 +503,23 @@ class TestProjectList(TestProject):
collist = ('ID', 'Name', 'Domain ID', 'Description', 'Enabled')
self.assertEqual(collist, columns)
datalist = ((
- identity_fakes.project_id,
- identity_fakes.project_name,
- identity_fakes.domain_id,
- identity_fakes.project_description,
+ self.project.id,
+ self.project.name,
+ self.project.domain_id,
+ self.project.description,
True,
), )
self.assertEqual(datalist, tuple(data))
def test_project_list_domain(self):
arglist = [
- '--domain', identity_fakes.domain_name,
+ '--domain', self.project.domain_id,
]
verifylist = [
- ('domain', identity_fakes.domain_name),
+ ('domain', self.project.domain_id),
]
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
+ self.domains_mock.get.return_value = self.domain
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -532,62 +528,54 @@ class TestProjectList(TestProject):
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
self.projects_mock.list.assert_called_with(
- domain=identity_fakes.domain_id)
+ domain=self.project.domain_id)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data))
def test_project_list_domain_no_perms(self):
arglist = [
- '--domain', identity_fakes.domain_id,
+ '--domain', self.project.domain_id,
]
verifylist = [
- ('domain', identity_fakes.domain_id),
+ ('domain', self.project.domain_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
mocker = mock.Mock()
mocker.return_value = None
- with mock.patch("openstackclient.common.utils.find_resource", mocker):
+ with mock.patch("osc_lib.utils.find_resource", mocker):
columns, data = self.cmd.take_action(parsed_args)
self.projects_mock.list.assert_called_with(
- domain=identity_fakes.domain_id)
+ domain=self.project.domain_id)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data))
class TestProjectSet(TestProject):
+ domain = identity_fakes.FakeDomain.create_one_domain()
+ project = identity_fakes.FakeProject.create_one_project(
+ attrs={'domain_id': domain.id})
+
def setUp(self):
super(TestProjectSet, self).setUp()
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
+ self.domains_mock.get.return_value = self.domain
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
- self.projects_mock.update.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
+ self.projects_mock.update.return_value = self.project
# Get the command object to test
self.cmd = project.SetProject(self.app, None)
def test_project_set_no_options(self):
arglist = [
- identity_fakes.project_name,
+ self.project.name,
]
verifylist = [
- ('project', identity_fakes.project_name),
+ ('project', self.project.name),
('enable', False),
('disable', False),
]
@@ -600,15 +588,15 @@ class TestProjectSet(TestProject):
def test_project_set_name(self):
arglist = [
'--name', 'qwerty',
- '--domain', identity_fakes.domain_id,
- identity_fakes.project_name,
+ '--domain', self.project.domain_id,
+ self.project.name,
]
verifylist = [
('name', 'qwerty'),
- ('domain', identity_fakes.domain_id),
+ ('domain', self.project.domain_id),
('enable', False),
('disable', False),
- ('project', identity_fakes.project_name),
+ ('project', self.project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -621,23 +609,23 @@ class TestProjectSet(TestProject):
# ProjectManager.update(project, name=, domain=, description=,
# enabled=, **kwargs)
self.projects_mock.update.assert_called_with(
- identity_fakes.project_id,
+ self.project.id,
**kwargs
)
self.assertIsNone(result)
def test_project_set_description(self):
arglist = [
- '--domain', identity_fakes.domain_id,
+ '--domain', self.project.domain_id,
'--description', 'new desc',
- identity_fakes.project_name,
+ self.project.name,
]
verifylist = [
- ('domain', identity_fakes.domain_id),
+ ('domain', self.project.domain_id),
('description', 'new desc'),
('enable', False),
('disable', False),
- ('project', identity_fakes.project_name),
+ ('project', self.project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -648,22 +636,22 @@ class TestProjectSet(TestProject):
'description': 'new desc',
}
self.projects_mock.update.assert_called_with(
- identity_fakes.project_id,
+ self.project.id,
**kwargs
)
self.assertIsNone(result)
def test_project_set_enable(self):
arglist = [
- '--domain', identity_fakes.domain_id,
+ '--domain', self.project.domain_id,
'--enable',
- identity_fakes.project_name,
+ self.project.name,
]
verifylist = [
- ('domain', identity_fakes.domain_id),
+ ('domain', self.project.domain_id),
('enable', True),
('disable', False),
- ('project', identity_fakes.project_name),
+ ('project', self.project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -674,22 +662,22 @@ class TestProjectSet(TestProject):
'enabled': True,
}
self.projects_mock.update.assert_called_with(
- identity_fakes.project_id,
+ self.project.id,
**kwargs
)
self.assertIsNone(result)
def test_project_set_disable(self):
arglist = [
- '--domain', identity_fakes.domain_id,
+ '--domain', self.project.domain_id,
'--disable',
- identity_fakes.project_name,
+ self.project.name,
]
verifylist = [
- ('domain', identity_fakes.domain_id),
+ ('domain', self.project.domain_id),
('enable', False),
('disable', True),
- ('project', identity_fakes.project_name),
+ ('project', self.project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -700,22 +688,22 @@ class TestProjectSet(TestProject):
'enabled': False,
}
self.projects_mock.update.assert_called_with(
- identity_fakes.project_id,
+ self.project.id,
**kwargs
)
self.assertIsNone(result)
def test_project_set_property(self):
arglist = [
- '--domain', identity_fakes.domain_id,
+ '--domain', self.project.domain_id,
'--property', 'fee=fi',
'--property', 'fo=fum',
- identity_fakes.project_name,
+ self.project.name,
]
verifylist = [
- ('domain', identity_fakes.domain_id),
+ ('domain', self.project.domain_id),
('property', {'fee': 'fi', 'fo': 'fum'}),
- ('project', identity_fakes.project_name),
+ ('project', self.project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -727,7 +715,7 @@ class TestProjectSet(TestProject):
'fo': 'fum',
}
self.projects_mock.update.assert_called_with(
- identity_fakes.project_id,
+ self.project.id,
**kwargs
)
self.assertIsNone(result)
@@ -735,71 +723,107 @@ class TestProjectSet(TestProject):
class TestProjectShow(TestProject):
+ domain = identity_fakes.FakeDomain.create_one_domain()
+
def setUp(self):
super(TestProjectShow, self).setUp()
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.project = identity_fakes.FakeProject.create_one_project(
+ attrs={'domain_id': self.domain.id})
# Get the command object to test
self.cmd = project.ShowProject(self.app, None)
def test_project_show(self):
+
+ self.projects_mock.get.side_effect = [Exception("Not found"),
+ self.project]
+ self.projects_mock.get.return_value = self.project
+
arglist = [
- identity_fakes.project_id,
+ self.project.id,
]
verifylist = [
- ('project', identity_fakes.project_id),
+ ('project', self.project.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.identity.tokens.get_token_data.return_value = \
+ {'token':
+ {'project':
+ {'domain': {},
+ 'name': parsed_args.project,
+ 'id': parsed_args.project
+ }
+ }
+ }
+
# In base command class ShowOne in cliff, abstract method take_action()
# returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
+
self.projects_mock.get.assert_called_with(
- identity_fakes.project_id,
+ self.project.id,
parents_as_list=False,
subtree_as_list=False,
)
- collist = ('description', 'domain_id', 'enabled', 'id', 'name')
+ collist = (
+ 'description',
+ 'domain_id',
+ 'enabled',
+ 'id',
+ 'is_domain',
+ 'name',
+ 'parent_id',
+ )
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.project_description,
- identity_fakes.domain_id,
+ self.project.description,
+ self.project.domain_id,
True,
- identity_fakes.project_id,
- identity_fakes.project_name,
+ self.project.id,
+ False,
+ self.project.name,
+ self.project.parent_id,
)
self.assertEqual(datalist, data)
def test_project_show_parents(self):
- project = copy.deepcopy(identity_fakes.PROJECT_WITH_GRANDPARENT)
- project['parents'] = identity_fakes.grandparents
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- project,
- loaded=True,
+ self.project = identity_fakes.FakeProject.create_one_project(
+ attrs={
+ 'parent_id': self.project.parent_id,
+ 'parents': [{'project': {'id': self.project.parent_id}}]
+ }
)
+ self.projects_mock.get.side_effect = [Exception("Not found"),
+ self.project]
+ self.projects_mock.get.return_value = self.project
arglist = [
- identity_fakes.PROJECT_WITH_GRANDPARENT['id'],
+ self.project.id,
'--parents',
]
verifylist = [
- ('project', identity_fakes.PROJECT_WITH_GRANDPARENT['id']),
+ ('project', self.project.id),
('parents', True),
('children', False),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.identity.tokens.get_token_data.return_value = \
+ {'token':
+ {'project':
+ {'domain': {},
+ 'name': parsed_args.project,
+ 'id': parsed_args.project
+ }
+ }
+ }
columns, data = self.cmd.take_action(parsed_args)
self.projects_mock.get.assert_called_with(
- identity_fakes.PROJECT_WITH_GRANDPARENT['id'],
+ self.project.id,
parents_as_list=True,
subtree_as_list=False,
)
@@ -809,45 +833,58 @@ class TestProjectShow(TestProject):
'domain_id',
'enabled',
'id',
+ 'is_domain',
'name',
'parent_id',
'parents',
)
self.assertEqual(columns, collist)
datalist = (
- identity_fakes.PROJECT_WITH_GRANDPARENT['description'],
- identity_fakes.PROJECT_WITH_GRANDPARENT['domain_id'],
- identity_fakes.PROJECT_WITH_GRANDPARENT['enabled'],
- identity_fakes.PROJECT_WITH_GRANDPARENT['id'],
- identity_fakes.PROJECT_WITH_GRANDPARENT['name'],
- identity_fakes.PROJECT_WITH_GRANDPARENT['parent_id'],
- identity_fakes.ids_for_parents_and_grandparents,
+ self.project.description,
+ self.project.domain_id,
+ self.project.enabled,
+ self.project.id,
+ self.project.is_domain,
+ self.project.name,
+ self.project.parent_id,
+ [self.project.parent_id],
)
self.assertEqual(data, datalist)
def test_project_show_subtree(self):
- project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT)
- project['subtree'] = identity_fakes.children
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- project,
- loaded=True,
+ self.project = identity_fakes.FakeProject.create_one_project(
+ attrs={
+ 'parent_id': self.project.parent_id,
+ 'subtree': [{'project': {'id': 'children-id'}}]
+ }
)
+ self.projects_mock.get.side_effect = [Exception("Not found"),
+ self.project]
+ self.projects_mock.get.return_value = self.project
arglist = [
- identity_fakes.PROJECT_WITH_PARENT['id'],
+ self.project.id,
'--children',
]
verifylist = [
- ('project', identity_fakes.PROJECT_WITH_PARENT['id']),
+ ('project', self.project.id),
('parents', False),
('children', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.identity.tokens.get_token_data.return_value = \
+ {'token':
+ {'project':
+ {'domain': {},
+ 'name': parsed_args.project,
+ 'id': parsed_args.project
+ }
+ }
+ }
columns, data = self.cmd.take_action(parsed_args)
self.projects_mock.get.assert_called_with(
- identity_fakes.PROJECT_WITH_PARENT['id'],
+ self.project.id,
parents_as_list=False,
subtree_as_list=True,
)
@@ -857,47 +894,60 @@ class TestProjectShow(TestProject):
'domain_id',
'enabled',
'id',
+ 'is_domain',
'name',
'parent_id',
'subtree',
)
self.assertEqual(columns, collist)
datalist = (
- identity_fakes.PROJECT_WITH_PARENT['description'],
- identity_fakes.PROJECT_WITH_PARENT['domain_id'],
- identity_fakes.PROJECT_WITH_PARENT['enabled'],
- identity_fakes.PROJECT_WITH_PARENT['id'],
- identity_fakes.PROJECT_WITH_PARENT['name'],
- identity_fakes.PROJECT_WITH_PARENT['parent_id'],
- identity_fakes.ids_for_children,
+ self.project.description,
+ self.project.domain_id,
+ self.project.enabled,
+ self.project.id,
+ self.project.is_domain,
+ self.project.name,
+ self.project.parent_id,
+ ['children-id'],
)
self.assertEqual(data, datalist)
def test_project_show_parents_and_children(self):
- project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT)
- project['subtree'] = identity_fakes.children
- project['parents'] = identity_fakes.parents
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- project,
- loaded=True,
+ self.project = identity_fakes.FakeProject.create_one_project(
+ attrs={
+ 'parent_id': self.project.parent_id,
+ 'parents': [{'project': {'id': self.project.parent_id}}],
+ 'subtree': [{'project': {'id': 'children-id'}}]
+ }
)
+ self.projects_mock.get.side_effect = [Exception("Not found"),
+ self.project]
+ self.projects_mock.get.return_value = self.project
arglist = [
- identity_fakes.PROJECT_WITH_PARENT['id'],
+ self.project.id,
'--parents',
'--children',
]
verifylist = [
- ('project', identity_fakes.PROJECT_WITH_PARENT['id']),
+ ('project', self.project.id),
('parents', True),
('children', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.identity.tokens.get_token_data.return_value = \
+ {'token':
+ {'project':
+ {'domain': {},
+ 'name': parsed_args.project,
+ 'id': parsed_args.project
+ }
+ }
+ }
columns, data = self.cmd.take_action(parsed_args)
self.projects_mock.get.assert_called_with(
- identity_fakes.PROJECT_WITH_PARENT['id'],
+ self.project.id,
parents_as_list=True,
subtree_as_list=True,
)
@@ -907,6 +957,7 @@ class TestProjectShow(TestProject):
'domain_id',
'enabled',
'id',
+ 'is_domain',
'name',
'parent_id',
'parents',
@@ -914,13 +965,14 @@ class TestProjectShow(TestProject):
)
self.assertEqual(columns, collist)
datalist = (
- identity_fakes.PROJECT_WITH_PARENT['description'],
- identity_fakes.PROJECT_WITH_PARENT['domain_id'],
- identity_fakes.PROJECT_WITH_PARENT['enabled'],
- identity_fakes.PROJECT_WITH_PARENT['id'],
- identity_fakes.PROJECT_WITH_PARENT['name'],
- identity_fakes.PROJECT_WITH_PARENT['parent_id'],
- identity_fakes.ids_for_parents,
- identity_fakes.ids_for_children,
+ self.project.description,
+ self.project.domain_id,
+ self.project.enabled,
+ self.project.id,
+ self.project.is_domain,
+ self.project.name,
+ self.project.parent_id,
+ [self.project.parent_id],
+ ['children-id'],
)
self.assertEqual(data, datalist)
diff --git a/openstackclient/tests/identity/v3/test_protocol.py b/openstackclient/tests/identity/v3/test_protocol.py
index 238b0ff..f718b27 100644
--- a/openstackclient/tests/identity/v3/test_protocol.py
+++ b/openstackclient/tests/identity/v3/test_protocol.py
@@ -88,7 +88,7 @@ class TestProtocolDelete(TestProtocol):
identity_fakes.protocol_id
]
verifylist = [
- ('federation_protocol', identity_fakes.protocol_id),
+ ('federation_protocol', [identity_fakes.protocol_id]),
('identity_provider', identity_fakes.idp_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
diff --git a/openstackclient/tests/identity/v3/test_region.py b/openstackclient/tests/identity/v3/test_region.py
index 02dec56..41ee5ce 100644
--- a/openstackclient/tests/identity/v3/test_region.py
+++ b/openstackclient/tests/identity/v3/test_region.py
@@ -153,7 +153,7 @@ class TestRegionDelete(TestRegion):
identity_fakes.region_id,
]
verifylist = [
- ('region', identity_fakes.region_id),
+ ('region', [identity_fakes.region_id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -253,7 +253,11 @@ class TestRegionSet(TestRegion):
result = self.cmd.take_action(parsed_args)
- self.assertNotCalled(self.regions_mock.update)
+ kwargs = {}
+ self.regions_mock.update.assert_called_with(
+ identity_fakes.region_id,
+ **kwargs
+ )
self.assertIsNone(result)
def test_region_set_description(self):
diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py
index d2398e5..b4e76d9 100644
--- a/openstackclient/tests/identity/v3/test_role.py
+++ b/openstackclient/tests/identity/v3/test_role.py
@@ -230,6 +230,45 @@ class TestRoleAdd(TestRole):
)
self.assertIsNone(result)
+ def test_role_add_domain_role_on_user_project(self):
+ self.roles_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.ROLE_2),
+ loaded=True,
+ )
+ arglist = [
+ '--user', identity_fakes.user_name,
+ '--project', identity_fakes.project_name,
+ '--role-domain', identity_fakes.domain_name,
+ identity_fakes.ROLE_2['name'],
+ ]
+ if self._is_inheritance_testcase():
+ arglist.append('--inherited')
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('group', None),
+ ('domain', None),
+ ('project', identity_fakes.project_name),
+ ('role', identity_fakes.ROLE_2['name']),
+ ('inherited', self._is_inheritance_testcase()),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'user': identity_fakes.user_id,
+ 'project': identity_fakes.project_id,
+ 'os_inherit_extension_inherited': self._is_inheritance_testcase(),
+ }
+ # RoleManager.grant(role, user=, group=, domain=, project=)
+ self.roles_mock.grant.assert_called_with(
+ identity_fakes.ROLE_2['id'],
+ **kwargs
+ )
+ self.assertIsNone(result)
+
class TestRoleAddInherited(TestRoleAdd, TestRoleInherited):
pass
@@ -240,6 +279,12 @@ class TestRoleCreate(TestRole):
def setUp(self):
super(TestRoleCreate, self).setUp()
+ self.domains_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.DOMAIN),
+ loaded=True,
+ )
+
self.roles_mock.create.return_value = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.ROLE),
@@ -265,22 +310,67 @@ class TestRoleCreate(TestRole):
# Set expected values
kwargs = {
+ 'domain': None,
'name': identity_fakes.role_name,
}
- # RoleManager.create(name=)
+ # RoleManager.create(name=, domain=)
self.roles_mock.create.assert_called_with(
**kwargs
)
- collist = ('id', 'name')
+ collist = ('domain', 'id', 'name')
self.assertEqual(collist, columns)
datalist = (
+ None,
identity_fakes.role_id,
identity_fakes.role_name,
)
self.assertEqual(datalist, data)
+ def test_role_create_with_domain(self):
+
+ self.roles_mock.create.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.ROLE_2),
+ loaded=True,
+ )
+
+ arglist = [
+ '--domain', identity_fakes.domain_name,
+ identity_fakes.ROLE_2['name'],
+ ]
+ verifylist = [
+ ('domain', identity_fakes.domain_name),
+ ('name', identity_fakes.ROLE_2['name']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class ShowOne in cliff, abstract method take_action()
+ # returns a two-part tuple with a tuple of column names and a tuple of
+ # data to be shown.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'domain': identity_fakes.domain_id,
+ 'name': identity_fakes.ROLE_2['name'],
+ }
+
+ # RoleManager.create(name=, domain=)
+ self.roles_mock.create.assert_called_with(
+ **kwargs
+ )
+
+ collist = ('domain', 'id', 'name')
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.domain_id,
+ identity_fakes.ROLE_2['id'],
+ identity_fakes.ROLE_2['name'],
+ )
+ self.assertEqual(datalist, data)
+
class TestRoleDelete(TestRole):
@@ -313,6 +403,31 @@ class TestRoleDelete(TestRole):
)
self.assertIsNone(result)
+ def test_role_delete_with_domain(self):
+ self.roles_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.ROLE_2),
+ loaded=True,
+ )
+ self.roles_mock.delete.return_value = None
+
+ arglist = [
+ '--domain', identity_fakes.domain_name,
+ identity_fakes.ROLE_2['name'],
+ ]
+ verifylist = [
+ ('roles', [identity_fakes.ROLE_2['name']]),
+ ('domain', identity_fakes.domain_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.roles_mock.delete.assert_called_with(
+ identity_fakes.ROLE_2['id'],
+ )
+ self.assertIsNone(result)
+
class TestRoleList(TestRole):
@@ -583,6 +698,45 @@ class TestRoleList(TestRole):
), )
self.assertEqual(datalist, tuple(data))
+ def test_role_list_domain_role(self):
+ self.roles_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.ROLE_2),
+ loaded=True,
+ ),
+ ]
+ arglist = [
+ '--domain', identity_fakes.domain_name,
+ ]
+ verifylist = [
+ ('domain', identity_fakes.domain_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'domain_id': identity_fakes.domain_id
+ }
+ # RoleManager.list(user=, group=, domain=, project=, **kwargs)
+ self.roles_mock.list.assert_called_with(
+ **kwargs
+ )
+
+ collist = ('ID', 'Name', 'Domain')
+ self.assertEqual(collist, columns)
+ datalist = ((
+ identity_fakes.ROLE_2['id'],
+ identity_fakes.ROLE_2['name'],
+ identity_fakes.domain_name,
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
class TestRoleRemove(TestRole):
@@ -756,6 +910,44 @@ class TestRoleRemove(TestRole):
)
self.assertIsNone(result)
+ def test_role_remove_domain_role_on_group_domain(self):
+ self.roles_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.ROLE_2),
+ loaded=True,
+ )
+ arglist = [
+ '--group', identity_fakes.group_name,
+ '--domain', identity_fakes.domain_name,
+ identity_fakes.ROLE_2['name'],
+ ]
+ if self._is_inheritance_testcase():
+ arglist.append('--inherited')
+ verifylist = [
+ ('user', None),
+ ('group', identity_fakes.group_name),
+ ('domain', identity_fakes.domain_name),
+ ('project', None),
+ ('role', identity_fakes.ROLE_2['name']),
+ ('inherited', self._is_inheritance_testcase()),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'group': identity_fakes.group_id,
+ 'domain': identity_fakes.domain_id,
+ 'os_inherit_extension_inherited': self._is_inheritance_testcase(),
+ }
+ # RoleManager.revoke(role, user=, group=, domain=, project=)
+ self.roles_mock.revoke.assert_called_with(
+ identity_fakes.ROLE_2['id'],
+ **kwargs
+ )
+ self.assertIsNone(result)
+
class TestRoleSet(TestRole):
@@ -796,6 +988,37 @@ class TestRoleSet(TestRole):
)
self.assertIsNone(result)
+ def test_role_set_domain_role(self):
+ self.roles_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.ROLE_2),
+ loaded=True,
+ )
+ arglist = [
+ '--name', 'over',
+ '--domain', identity_fakes.domain_name,
+ identity_fakes.ROLE_2['name'],
+ ]
+ verifylist = [
+ ('name', 'over'),
+ ('domain', identity_fakes.domain_name),
+ ('role', identity_fakes.ROLE_2['name']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'name': 'over',
+ }
+ # RoleManager.update(role, name=)
+ self.roles_mock.update.assert_called_with(
+ identity_fakes.ROLE_2['id'],
+ **kwargs
+ )
+ self.assertIsNone(result)
+
class TestRoleShow(TestRole):
@@ -830,10 +1053,53 @@ class TestRoleShow(TestRole):
identity_fakes.role_name,
)
- collist = ('id', 'name')
+ collist = ('domain', 'id', 'name')
self.assertEqual(collist, columns)
datalist = (
+ None,
identity_fakes.role_id,
identity_fakes.role_name,
)
self.assertEqual(datalist, data)
+
+ def test_role_show_domain_role(self):
+ self.roles_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.ROLE_2),
+ loaded=True,
+ )
+ arglist = [
+ '--domain', identity_fakes.domain_name,
+ identity_fakes.ROLE_2['name'],
+ ]
+ verifylist = [
+ ('domain', identity_fakes.domain_name),
+ ('role', identity_fakes.ROLE_2['name']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class ShowOne in cliff, abstract method take_action()
+ # returns a two-part tuple with a tuple of column names and a tuple of
+ # data to be shown.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # RoleManager.get(role). This is called from utils.find_resource().
+ # In fact, the current implementation calls the get(role) first with
+ # just the name, then with the name+domain_id. So technically we should
+ # mock this out with a call list, with the first call returning None
+ # and the second returning the object. However, if we did that we are
+ # then just testing the current sequencing within the utils method, and
+ # would become brittle to changes within that method. Hence we just
+ # check for the first call which is always lookup by name.
+ self.roles_mock.get.assert_called_with(
+ identity_fakes.ROLE_2['name'],
+ )
+
+ collist = ('domain', 'id', 'name')
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.domain_id,
+ identity_fakes.ROLE_2['id'],
+ identity_fakes.ROLE_2['name'],
+ )
+ self.assertEqual(datalist, data)
diff --git a/openstackclient/tests/identity/v3/test_role_assignment.py b/openstackclient/tests/identity/v3/test_role_assignment.py
index 8956b74..113cc49 100644
--- a/openstackclient/tests/identity/v3/test_role_assignment.py
+++ b/openstackclient/tests/identity/v3/test_role_assignment.py
@@ -12,6 +12,7 @@
#
import copy
+import mock
from openstackclient.identity.v3 import role_assignment
from openstackclient.tests import fakes
@@ -374,6 +375,65 @@ class TestRoleAssignmentList(TestRoleAssignment):
),)
self.assertEqual(datalist, tuple(data))
+ def test_role_assignment_list_def_creds(self):
+
+ auth_ref = self.app.client_manager.auth_ref = mock.MagicMock()
+ auth_ref.project_id.return_value = identity_fakes.project_id
+ auth_ref.user_id.return_value = identity_fakes.user_id
+
+ self.role_assignments_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID),
+ loaded=True,
+ ),
+ ]
+
+ arglist = [
+ '--auth-user',
+ '--auth-project',
+ ]
+ verifylist = [
+ ('user', None),
+ ('group', None),
+ ('domain', None),
+ ('project', None),
+ ('role', None),
+ ('effective', False),
+ ('inherited', False),
+ ('names', False),
+ ('authuser', True),
+ ('authproject', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.role_assignments_mock.list.assert_called_with(
+ domain=None,
+ user=self.users_mock.get(),
+ group=None,
+ project=self.projects_mock.get(),
+ role=None,
+ effective=False,
+ os_inherit_extension_inherited_to=None,
+ include_names=False)
+
+ self.assertEqual(self.columns, columns)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.user_id,
+ '',
+ identity_fakes.project_id,
+ '',
+ False
+ ),)
+ self.assertEqual(datalist, tuple(data))
+
def test_role_assignment_list_effective(self):
self.role_assignments_mock.list.return_value = [
@@ -568,3 +628,56 @@ class TestRoleAssignmentList(TestRoleAssignment):
False
),)
self.assertEqual(tuple(data), datalist1)
+
+ def test_role_assignment_list_domain_role(self):
+
+ self.role_assignments_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(
+ identity_fakes.ASSIGNMENT_WITH_DOMAIN_ROLE),
+ loaded=True,
+ ),
+ ]
+
+ arglist = [
+ '--role', identity_fakes.ROLE_2['name'],
+ '--role-domain', identity_fakes.domain_name
+ ]
+ verifylist = [
+ ('user', None),
+ ('group', None),
+ ('domain', None),
+ ('project', None),
+ ('role', identity_fakes.ROLE_2['name']),
+ ('effective', False),
+ ('inherited', False),
+ ('names', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.role_assignments_mock.list.assert_called_with(
+ domain=None,
+ user=None,
+ group=None,
+ project=None,
+ role=self.roles_mock.get(),
+ effective=False,
+ os_inherit_extension_inherited_to=None,
+ include_names=False)
+
+ self.assertEqual(self.columns, columns)
+ datalist = ((
+ identity_fakes.ROLE_2['id'],
+ identity_fakes.user_id,
+ '',
+ '',
+ identity_fakes.domain_id,
+ False
+ ),)
+ self.assertEqual(datalist, tuple(data))
diff --git a/openstackclient/tests/identity/v3/test_service.py b/openstackclient/tests/identity/v3/test_service.py
index 1e70383..76c66aa 100644
--- a/openstackclient/tests/identity/v3/test_service.py
+++ b/openstackclient/tests/identity/v3/test_service.py
@@ -13,10 +13,10 @@
# under the License.
#
-import copy
+from keystoneclient import exceptions as identity_exc
+from osc_lib import exceptions
from openstackclient.identity.v3 import service
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
@@ -39,37 +39,34 @@ class TestServiceCreate(TestService):
'name',
'type',
)
- datalist = (
- identity_fakes.service_description,
- True,
- identity_fakes.service_id,
- identity_fakes.service_name,
- identity_fakes.service_type,
- )
def setUp(self):
super(TestServiceCreate, self).setUp()
- self.services_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
+ self.service = identity_fakes.FakeService.create_one_service()
+ self.datalist = (
+ self.service.description,
+ True,
+ self.service.id,
+ self.service.name,
+ self.service.type,
)
+ self.services_mock.create.return_value = self.service
# Get the command object to test
self.cmd = service.CreateService(self.app, None)
def test_service_create_name(self):
arglist = [
- '--name', identity_fakes.service_name,
- identity_fakes.service_type,
+ '--name', self.service.name,
+ self.service.type,
]
verifylist = [
- ('name', identity_fakes.service_name),
+ ('name', self.service.name),
('description', None),
('enable', False),
('disable', False),
- ('type', identity_fakes.service_type),
+ ('type', self.service.type),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -80,8 +77,8 @@ class TestServiceCreate(TestService):
# ServiceManager.create(name=, type=, enabled=, **kwargs)
self.services_mock.create.assert_called_with(
- name=identity_fakes.service_name,
- type=identity_fakes.service_type,
+ name=self.service.name,
+ type=self.service.type,
description=None,
enabled=True,
)
@@ -91,15 +88,15 @@ class TestServiceCreate(TestService):
def test_service_create_description(self):
arglist = [
- '--description', identity_fakes.service_description,
- identity_fakes.service_type,
+ '--description', self.service.description,
+ self.service.type,
]
verifylist = [
('name', None),
- ('description', identity_fakes.service_description),
+ ('description', self.service.description),
('enable', False),
('disable', False),
- ('type', identity_fakes.service_type),
+ ('type', self.service.type),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -111,8 +108,8 @@ class TestServiceCreate(TestService):
# ServiceManager.create(name=, type=, enabled=, **kwargs)
self.services_mock.create.assert_called_with(
name=None,
- type=identity_fakes.service_type,
- description=identity_fakes.service_description,
+ type=self.service.type,
+ description=self.service.description,
enabled=True,
)
@@ -122,14 +119,14 @@ class TestServiceCreate(TestService):
def test_service_create_enable(self):
arglist = [
'--enable',
- identity_fakes.service_type,
+ self.service.type,
]
verifylist = [
('name', None),
('description', None),
('enable', True),
('disable', False),
- ('type', identity_fakes.service_type),
+ ('type', self.service.type),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -141,7 +138,7 @@ class TestServiceCreate(TestService):
# ServiceManager.create(name=, type=, enabled=, **kwargs)
self.services_mock.create.assert_called_with(
name=None,
- type=identity_fakes.service_type,
+ type=self.service.type,
description=None,
enabled=True,
)
@@ -152,14 +149,14 @@ class TestServiceCreate(TestService):
def test_service_create_disable(self):
arglist = [
'--disable',
- identity_fakes.service_type,
+ self.service.type,
]
verifylist = [
('name', None),
('description', None),
('enable', False),
('disable', True),
- ('type', identity_fakes.service_type),
+ ('type', self.service.type),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -171,7 +168,7 @@ class TestServiceCreate(TestService):
# ServiceManager.create(name=, type=, enabled=, **kwargs)
self.services_mock.create.assert_called_with(
name=None,
- type=identity_fakes.service_type,
+ type=self.service.type,
description=None,
enabled=False,
)
@@ -182,14 +179,13 @@ class TestServiceCreate(TestService):
class TestServiceDelete(TestService):
+ service = identity_fakes.FakeService.create_one_service()
+
def setUp(self):
super(TestServiceDelete, self).setUp()
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
+ self.services_mock.get.side_effect = identity_exc.NotFound(None)
+ self.services_mock.find.return_value = self.service
self.services_mock.delete.return_value = None
# Get the command object to test
@@ -197,33 +193,29 @@ class TestServiceDelete(TestService):
def test_service_delete_no_options(self):
arglist = [
- identity_fakes.service_name,
+ self.service.name,
]
verifylist = [
- ('service', identity_fakes.service_name),
+ ('service', [self.service.name]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.services_mock.delete.assert_called_with(
- identity_fakes.service_id,
+ self.service.id,
)
self.assertIsNone(result)
class TestServiceList(TestService):
+ service = identity_fakes.FakeService.create_one_service()
+
def setUp(self):
super(TestServiceList, self).setUp()
- self.services_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- ),
- ]
+ self.services_mock.list.return_value = [self.service]
# Get the command object to test
self.cmd = service.ListService(self.app, None)
@@ -243,9 +235,9 @@ class TestServiceList(TestService):
collist = ('ID', 'Name', 'Type')
self.assertEqual(collist, columns)
datalist = ((
- identity_fakes.service_id,
- identity_fakes.service_name,
- identity_fakes.service_type,
+ self.service.id,
+ self.service.name,
+ self.service.type,
), )
self.assertEqual(datalist, tuple(data))
@@ -268,10 +260,10 @@ class TestServiceList(TestService):
collist = ('ID', 'Name', 'Type', 'Description', 'Enabled')
self.assertEqual(collist, columns)
datalist = ((
- identity_fakes.service_id,
- identity_fakes.service_name,
- identity_fakes.service_type,
- identity_fakes.service_description,
+ self.service.id,
+ self.service.name,
+ self.service.type,
+ self.service.description,
True,
), )
self.assertEqual(datalist, tuple(data))
@@ -279,26 +271,21 @@ class TestServiceList(TestService):
class TestServiceSet(TestService):
+ service = identity_fakes.FakeService.create_one_service()
+
def setUp(self):
super(TestServiceSet, self).setUp()
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
- self.services_mock.update.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
+ self.services_mock.get.side_effect = identity_exc.NotFound(None)
+ self.services_mock.find.return_value = self.service
+ self.services_mock.update.return_value = self.service
# Get the command object to test
self.cmd = service.SetService(self.app, None)
def test_service_set_no_options(self):
arglist = [
- identity_fakes.service_name,
+ self.service.name,
]
verifylist = [
('type', None),
@@ -306,7 +293,7 @@ class TestServiceSet(TestService):
('description', None),
('enable', False),
('disable', False),
- ('service', identity_fakes.service_name),
+ ('service', self.service.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -316,16 +303,16 @@ class TestServiceSet(TestService):
def test_service_set_type(self):
arglist = [
- '--type', identity_fakes.service_type,
- identity_fakes.service_name,
+ '--type', self.service.type,
+ self.service.name,
]
verifylist = [
- ('type', identity_fakes.service_type),
+ ('type', self.service.type),
('name', None),
('description', None),
('enable', False),
('disable', False),
- ('service', identity_fakes.service_name),
+ ('service', self.service.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -333,27 +320,27 @@ class TestServiceSet(TestService):
# Set expected values
kwargs = {
- 'type': identity_fakes.service_type,
+ 'type': self.service.type,
}
# ServiceManager.update(service, name=, type=, enabled=, **kwargs)
self.services_mock.update.assert_called_with(
- identity_fakes.service_id,
+ self.service.id,
**kwargs
)
self.assertIsNone(result)
def test_service_set_name(self):
arglist = [
- '--name', identity_fakes.service_name,
- identity_fakes.service_name,
+ '--name', self.service.name,
+ self.service.name,
]
verifylist = [
('type', None),
- ('name', identity_fakes.service_name),
+ ('name', self.service.name),
('description', None),
('enable', False),
('disable', False),
- ('service', identity_fakes.service_name),
+ ('service', self.service.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -361,27 +348,27 @@ class TestServiceSet(TestService):
# Set expected values
kwargs = {
- 'name': identity_fakes.service_name,
+ 'name': self.service.name,
}
# ServiceManager.update(service, name=, type=, enabled=, **kwargs)
self.services_mock.update.assert_called_with(
- identity_fakes.service_id,
+ self.service.id,
**kwargs
)
self.assertIsNone(result)
def test_service_set_description(self):
arglist = [
- '--description', identity_fakes.service_description,
- identity_fakes.service_name,
+ '--description', self.service.description,
+ self.service.name,
]
verifylist = [
('type', None),
('name', None),
- ('description', identity_fakes.service_description),
+ ('description', self.service.description),
('enable', False),
('disable', False),
- ('service', identity_fakes.service_name),
+ ('service', self.service.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -389,11 +376,11 @@ class TestServiceSet(TestService):
# Set expected values
kwargs = {
- 'description': identity_fakes.service_description,
+ 'description': self.service.description,
}
# ServiceManager.update(service, name=, type=, enabled=, **kwargs)
self.services_mock.update.assert_called_with(
- identity_fakes.service_id,
+ self.service.id,
**kwargs
)
self.assertIsNone(result)
@@ -401,7 +388,7 @@ class TestServiceSet(TestService):
def test_service_set_enable(self):
arglist = [
'--enable',
- identity_fakes.service_name,
+ self.service.name,
]
verifylist = [
('type', None),
@@ -409,7 +396,7 @@ class TestServiceSet(TestService):
('description', None),
('enable', True),
('disable', False),
- ('service', identity_fakes.service_name),
+ ('service', self.service.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -421,7 +408,7 @@ class TestServiceSet(TestService):
}
# ServiceManager.update(service, name=, type=, enabled=, **kwargs)
self.services_mock.update.assert_called_with(
- identity_fakes.service_id,
+ self.service.id,
**kwargs
)
self.assertIsNone(result)
@@ -429,7 +416,7 @@ class TestServiceSet(TestService):
def test_service_set_disable(self):
arglist = [
'--disable',
- identity_fakes.service_name,
+ self.service.name,
]
verifylist = [
('type', None),
@@ -437,7 +424,7 @@ class TestServiceSet(TestService):
('description', None),
('enable', False),
('disable', True),
- ('service', identity_fakes.service_name),
+ ('service', self.service.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -449,7 +436,7 @@ class TestServiceSet(TestService):
}
# ServiceManager.update(service, name=, type=, enabled=, **kwargs)
self.services_mock.update.assert_called_with(
- identity_fakes.service_id,
+ self.service.id,
**kwargs
)
self.assertIsNone(result)
@@ -457,24 +444,23 @@ class TestServiceSet(TestService):
class TestServiceShow(TestService):
+ service = identity_fakes.FakeService.create_one_service()
+
def setUp(self):
super(TestServiceShow, self).setUp()
- self.services_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.SERVICE),
- loaded=True,
- )
+ self.services_mock.get.side_effect = identity_exc.NotFound(None)
+ self.services_mock.find.return_value = self.service
# Get the command object to test
self.cmd = service.ShowService(self.app, None)
def test_service_show(self):
arglist = [
- identity_fakes.service_name,
+ self.service.name,
]
verifylist = [
- ('service', identity_fakes.service_name),
+ ('service', self.service.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -484,17 +470,35 @@ class TestServiceShow(TestService):
columns, data = self.cmd.take_action(parsed_args)
# ServiceManager.get(id)
- self.services_mock.get.assert_called_with(
- identity_fakes.service_name,
+ self.services_mock.find.assert_called_with(
+ name=self.service.name
)
collist = ('description', 'enabled', 'id', 'name', 'type')
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.service_description,
+ self.service.description,
True,
- identity_fakes.service_id,
- identity_fakes.service_name,
- identity_fakes.service_type,
+ self.service.id,
+ self.service.name,
+ self.service.type,
)
self.assertEqual(datalist, data)
+
+ def test_service_show_nounique(self):
+ self.services_mock.find.side_effect = identity_exc.NoUniqueMatch(None)
+ arglist = [
+ 'nounique_service',
+ ]
+ verifylist = [
+ ('service', 'nounique_service'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual(
+ "Multiple service matches found for 'nounique_service',"
+ " use an ID to be more specific.", str(e))
diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py
index 99ea1f7..873ab1e 100644
--- a/openstackclient/tests/identity/v3/test_service_provider.py
+++ b/openstackclient/tests/identity/v3/test_service_provider.py
@@ -185,7 +185,7 @@ class TestServiceProviderDelete(TestServiceProvider):
service_fakes.sp_id,
]
verifylist = [
- ('service_provider', service_fakes.sp_id),
+ ('service_provider', [service_fakes.sp_id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -289,7 +289,7 @@ class TestServiceProviderSet(TestServiceProvider):
('disable', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- columns, data = self.cmd.take_action(parsed_args)
+ self.cmd.take_action(parsed_args)
self.service_providers_mock.update.assert_called_with(
service_fakes.sp_id,
enabled=False,
@@ -298,9 +298,6 @@ class TestServiceProviderSet(TestServiceProvider):
sp_url=None
)
- self.assertEqual(self.columns, columns)
- self.assertEqual(self.datalist, data)
-
def test_service_provider_enable(self):
"""Enable Service Provider.
@@ -327,19 +324,10 @@ class TestServiceProviderSet(TestServiceProvider):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- columns, data = self.cmd.take_action(parsed_args)
+ self.cmd.take_action(parsed_args)
self.service_providers_mock.update.assert_called_with(
service_fakes.sp_id, enabled=True, description=None,
auth_url=None, sp_url=None)
- self.assertEqual(self.columns, columns)
- datalist = (
- service_fakes.sp_auth_url,
- service_fakes.sp_description,
- True,
- service_fakes.sp_id,
- service_fakes.service_provider_url
- )
- self.assertEqual(datalist, data)
def test_service_provider_no_options(self):
def prepare(self):
@@ -372,13 +360,7 @@ class TestServiceProviderSet(TestServiceProvider):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- columns, data = self.cmd.take_action(parsed_args)
-
- # expect take_action() to return (None, None) as none of --disabled,
- # --enabled, --description, --service-provider-url, --auth_url option
- # was set.
- self.assertIsNone(columns)
- self.assertIsNone(data)
+ self.cmd.take_action(parsed_args)
class TestServiceProviderShow(TestServiceProvider):
@@ -391,7 +373,10 @@ class TestServiceProviderShow(TestServiceProvider):
copy.deepcopy(service_fakes.SERVICE_PROVIDER),
loaded=True,
)
+ self.service_providers_mock.get.side_effect = [Exception("Not found"),
+ ret]
self.service_providers_mock.get.return_value = ret
+
# Get the command object to test
self.cmd = service_provider.ShowServiceProvider(self.app, None)
diff --git a/openstackclient/tests/identity/v3/test_token.py b/openstackclient/tests/identity/v3/test_token.py
index b68bc24..9728c6e 100644
--- a/openstackclient/tests/identity/v3/test_token.py
+++ b/openstackclient/tests/identity/v3/test_token.py
@@ -24,10 +24,9 @@ class TestToken(identity_fakes.TestIdentityv3):
def setUp(self):
super(TestToken, self).setUp()
- # Get a shortcut to the Service Catalog Mock
- self.sc_mock = mock.Mock()
- self.app.client_manager.auth_ref = mock.Mock()
- self.app.client_manager.auth_ref.service_catalog = self.sc_mock
+ # Get a shortcut to the Auth Ref Mock
+ self.ar_mock = mock.PropertyMock()
+ type(self.app.client_manager).auth_ref = self.ar_mock
class TestTokenIssue(TestToken):
@@ -38,23 +37,25 @@ class TestTokenIssue(TestToken):
self.cmd = token.IssueToken(self.app, None)
def test_token_issue_with_project_id(self):
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.TOKEN_WITH_PROJECT_ID,
+ )
+ self.ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = self.ar_mock
+
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.sc_mock.get_token.return_value = \
- identity_fakes.TOKEN_WITH_PROJECT_ID
# In base command class ShowOne in cliff, abstract method take_action()
# returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
- self.sc_mock.get_token.assert_called_with()
-
collist = ('expires', 'id', 'project_id', 'user_id')
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.token_expires,
+ auth_ref.expires,
identity_fakes.token_id,
identity_fakes.project_id,
identity_fakes.user_id,
@@ -62,45 +63,53 @@ class TestTokenIssue(TestToken):
self.assertEqual(datalist, data)
def test_token_issue_with_domain_id(self):
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.TOKEN_WITH_DOMAIN_ID,
+ )
+ self.ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = self.ar_mock
+
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.sc_mock.get_token.return_value = \
- identity_fakes.TOKEN_WITH_DOMAIN_ID
# In base command class ShowOne in cliff, abstract method take_action()
# returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
- self.sc_mock.get_token.assert_called_with()
-
collist = ('domain_id', 'expires', 'id', 'user_id')
self.assertEqual(collist, columns)
datalist = (
identity_fakes.domain_id,
- identity_fakes.token_expires,
+ auth_ref.expires,
identity_fakes.token_id,
identity_fakes.user_id,
)
self.assertEqual(datalist, data)
def test_token_issue_with_unscoped(self):
+ auth_ref = identity_fakes.fake_auth_ref(
+ identity_fakes.UNSCOPED_TOKEN,
+ )
+ self.ar_mock = mock.PropertyMock(return_value=auth_ref)
+ type(self.app.client_manager).auth_ref = self.ar_mock
+
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.sc_mock.get_token.return_value = \
- identity_fakes.UNSCOPED_TOKEN
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
- self.sc_mock.get_token.assert_called_with()
-
- collist = ('expires', 'id', 'user_id')
+ collist = (
+ 'expires',
+ 'id',
+ 'user_id',
+ )
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.token_expires,
+ auth_ref.expires,
identity_fakes.token_id,
identity_fakes.user_id,
)
diff --git a/openstackclient/tests/identity/v3/test_unscoped_saml.py b/openstackclient/tests/identity/v3/test_unscoped_saml.py
index d12cb45..6262390 100644
--- a/openstackclient/tests/identity/v3/test_unscoped_saml.py
+++ b/openstackclient/tests/identity/v3/test_unscoped_saml.py
@@ -12,7 +12,8 @@
import copy
-from openstackclient.common import exceptions
+from osc_lib import exceptions
+
from openstackclient.identity.v3 import unscoped_saml
from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py
index 5dafa77..c3d9e74 100644
--- a/openstackclient/tests/identity/v3/test_user.py
+++ b/openstackclient/tests/identity/v3/test_user.py
@@ -14,12 +14,9 @@
#
import contextlib
-import copy
-
import mock
from openstackclient.identity.v3 import user
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
@@ -52,6 +49,9 @@ class TestUser(identity_fakes.TestIdentityv3):
class TestUserCreate(TestUser):
+ domain = identity_fakes.FakeDomain.create_one_domain()
+ project = identity_fakes.FakeProject.create_one_project()
+
columns = (
'default_project_id',
'domain_id',
@@ -60,47 +60,38 @@ class TestUserCreate(TestUser):
'id',
'name',
)
- datalist = (
- identity_fakes.project_id,
- identity_fakes.domain_id,
- identity_fakes.user_email,
- True,
- identity_fakes.user_id,
- identity_fakes.user_name,
- )
def setUp(self):
super(TestUserCreate, self).setUp()
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
+ self.user = identity_fakes.FakeUser.create_one_user(
+ attrs={'domain_id': self.domain.id,
+ 'default_project_id': self.project.id}
)
-
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
+ self.datalist = (
+ self.project.id,
+ self.domain.id,
+ self.user.email,
+ True,
+ self.user.id,
+ self.user.name,
)
- self.users_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.domains_mock.get.return_value = self.domain
+ self.projects_mock.get.return_value = self.project
+ self.users_mock.create.return_value = self.user
# Get the command object to test
self.cmd = user.CreateUser(self.app, None)
def test_user_create_no_options(self):
arglist = [
- identity_fakes.user_name,
+ self.user.name,
]
verifylist = [
('enable', False),
('disable', False),
- ('name', identity_fakes.user_name),
+ ('name', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -111,7 +102,7 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
- 'name': identity_fakes.user_name,
+ 'name': self.user.name,
'default_project': None,
'description': None,
'domain': None,
@@ -132,14 +123,14 @@ class TestUserCreate(TestUser):
def test_user_create_password(self):
arglist = [
'--password', 'secret',
- identity_fakes.user_name,
+ self.user.name,
]
verifylist = [
('password', 'secret'),
('password_prompt', False),
('enable', False),
('disable', False),
- ('name', identity_fakes.user_name),
+ ('name', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -150,7 +141,7 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
- 'name': identity_fakes.user_name,
+ 'name': self.user.name,
'default_project': None,
'description': None,
'domain': None,
@@ -169,14 +160,14 @@ class TestUserCreate(TestUser):
def test_user_create_password_prompt(self):
arglist = [
'--password-prompt',
- identity_fakes.user_name,
+ self.user.name,
]
verifylist = [
('password', None),
('password_prompt', True),
('enable', False),
('disable', False),
- ('name', identity_fakes.user_name),
+ ('name', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -185,12 +176,12 @@ class TestUserCreate(TestUser):
# data to be shown.
mocker = mock.Mock()
mocker.return_value = 'abc123'
- with mock.patch("openstackclient.common.utils.get_password", mocker):
+ with mock.patch("osc_lib.utils.get_password", mocker):
columns, data = self.cmd.take_action(parsed_args)
# Set expected values
kwargs = {
- 'name': identity_fakes.user_name,
+ 'name': self.user.name,
'default_project': None,
'description': None,
'domain': None,
@@ -210,13 +201,13 @@ class TestUserCreate(TestUser):
def test_user_create_email(self):
arglist = [
'--email', 'barney@example.com',
- identity_fakes.user_name,
+ self.user.name,
]
verifylist = [
('email', 'barney@example.com'),
('enable', False),
('disable', False),
- ('name', identity_fakes.user_name),
+ ('name', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -227,7 +218,7 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
- 'name': identity_fakes.user_name,
+ 'name': self.user.name,
'default_project': None,
'description': None,
'domain': None,
@@ -245,30 +236,15 @@ class TestUserCreate(TestUser):
self.assertEqual(self.datalist, data)
def test_user_create_project(self):
- # Return the new project
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT_2),
- loaded=True,
- )
- # Set up to return an updated user
- USER_2 = copy.deepcopy(identity_fakes.USER)
- USER_2['default_project_id'] = identity_fakes.PROJECT_2['id']
- self.users_mock.create.return_value = fakes.FakeResource(
- None,
- USER_2,
- loaded=True,
- )
-
arglist = [
- '--project', identity_fakes.PROJECT_2['name'],
- identity_fakes.user_name,
+ '--project', self.project.name,
+ self.user.name,
]
verifylist = [
- ('project', identity_fakes.PROJECT_2['name']),
+ ('project', self.project.name),
('enable', False),
('disable', False),
- ('name', identity_fakes.user_name),
+ ('name', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -279,8 +255,8 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
- 'name': identity_fakes.user_name,
- 'default_project': identity_fakes.PROJECT_2['id'],
+ 'name': self.user.name,
+ 'default_project': self.project.id,
'description': None,
'domain': None,
'email': None,
@@ -295,42 +271,27 @@ class TestUserCreate(TestUser):
self.assertEqual(self.columns, columns)
datalist = (
- identity_fakes.PROJECT_2['id'],
- identity_fakes.domain_id,
- identity_fakes.user_email,
+ self.project.id,
+ self.domain.id,
+ self.user.email,
True,
- identity_fakes.user_id,
- identity_fakes.user_name,
+ self.user.id,
+ self.user.name,
)
self.assertEqual(datalist, data)
def test_user_create_project_domain(self):
- # Return the new project
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT_2),
- loaded=True,
- )
- # Set up to return an updated user
- USER_2 = copy.deepcopy(identity_fakes.USER)
- USER_2['default_project_id'] = identity_fakes.PROJECT_2['id']
- self.users_mock.create.return_value = fakes.FakeResource(
- None,
- USER_2,
- loaded=True,
- )
-
arglist = [
- '--project', identity_fakes.PROJECT_2['name'],
- '--project-domain', identity_fakes.PROJECT_2['domain_id'],
- identity_fakes.user_name,
+ '--project', self.project.name,
+ '--project-domain', self.project.domain_id,
+ self.user.name,
]
verifylist = [
- ('project', identity_fakes.PROJECT_2['name']),
- ('project_domain', identity_fakes.PROJECT_2['domain_id']),
+ ('project', self.project.name),
+ ('project_domain', self.project.domain_id),
('enable', False),
('disable', False),
- ('name', identity_fakes.user_name),
+ ('name', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -341,8 +302,8 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
- 'name': identity_fakes.user_name,
- 'default_project': identity_fakes.PROJECT_2['id'],
+ 'name': self.user.name,
+ 'default_project': self.project.id,
'description': None,
'domain': None,
'email': None,
@@ -357,25 +318,25 @@ class TestUserCreate(TestUser):
self.assertEqual(self.columns, columns)
datalist = (
- identity_fakes.PROJECT_2['id'],
- identity_fakes.domain_id,
- identity_fakes.user_email,
+ self.project.id,
+ self.domain.id,
+ self.user.email,
True,
- identity_fakes.user_id,
- identity_fakes.user_name,
+ self.user.id,
+ self.user.name,
)
self.assertEqual(datalist, data)
def test_user_create_domain(self):
arglist = [
- '--domain', identity_fakes.domain_name,
- identity_fakes.user_name,
+ '--domain', self.domain.name,
+ self.user.name,
]
verifylist = [
- ('domain', identity_fakes.domain_name),
+ ('domain', self.domain.name),
('enable', False),
('disable', False),
- ('name', identity_fakes.user_name),
+ ('name', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -386,10 +347,10 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
- 'name': identity_fakes.user_name,
+ 'name': self.user.name,
'default_project': None,
'description': None,
- 'domain': identity_fakes.domain_id,
+ 'domain': self.domain.id,
'email': None,
'enabled': True,
'password': None,
@@ -406,12 +367,12 @@ class TestUserCreate(TestUser):
def test_user_create_enable(self):
arglist = [
'--enable',
- identity_fakes.user_name,
+ self.user.name,
]
verifylist = [
('enable', True),
('disable', False),
- ('name', identity_fakes.user_name),
+ ('name', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -422,7 +383,7 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
- 'name': identity_fakes.user_name,
+ 'name': self.user.name,
'default_project': None,
'description': None,
'domain': None,
@@ -442,10 +403,10 @@ class TestUserCreate(TestUser):
def test_user_create_disable(self):
arglist = [
'--disable',
- identity_fakes.user_name,
+ self.user.name,
]
verifylist = [
- ('name', identity_fakes.user_name),
+ ('name', self.user.name),
('enable', False),
('disable', True),
]
@@ -458,7 +419,7 @@ class TestUserCreate(TestUser):
# Set expected values
kwargs = {
- 'name': identity_fakes.user_name,
+ 'name': self.user.name,
'default_project': None,
'description': None,
'domain': None,
@@ -476,15 +437,13 @@ class TestUserCreate(TestUser):
class TestUserDelete(TestUser):
+ user = identity_fakes.FakeUser.create_one_user()
+
def setUp(self):
super(TestUserDelete, self).setUp()
# This is the return value for utils.find_resource()
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.users_mock.get.return_value = self.user
self.users_mock.delete.return_value = None
# Get the command object to test
@@ -492,76 +451,54 @@ class TestUserDelete(TestUser):
def test_user_delete_no_options(self):
arglist = [
- identity_fakes.user_id,
+ self.user.id,
]
verifylist = [
- ('users', [identity_fakes.user_id]),
+ ('users', [self.user.id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.users_mock.delete.assert_called_with(
- identity_fakes.user_id,
+ self.user.id,
)
self.assertIsNone(result)
class TestUserList(TestUser):
+ domain = identity_fakes.FakeDomain.create_one_domain()
+ project = identity_fakes.FakeProject.create_one_project()
+ user = identity_fakes.FakeUser.create_one_user(
+ attrs={'domain_id': domain.id,
+ 'default_project_id': project.id}
+ )
+ group = identity_fakes.FakeGroup.create_one_group()
+ role_assignment = (
+ identity_fakes.FakeRoleAssignment.create_one_role_assignment(
+ attrs={'user': {'id': user.id}}))
+
columns = [
'ID',
'Name'
]
datalist = (
(
- identity_fakes.user_id,
- identity_fakes.user_name,
+ user.id,
+ user.name,
),
)
def setUp(self):
super(TestUserList, self).setUp()
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
- self.users_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- ),
- ]
-
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
-
- self.groups_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.GROUP),
- loaded=True,
- )
-
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
-
- self.role_assignments_mock.list.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(
- identity_fakes.ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID),
- loaded=True,
- )
- ]
+ self.users_mock.get.return_value = self.user
+ self.users_mock.list.return_value = [self.user]
+ self.domains_mock.get.return_value = self.domain
+ self.groups_mock.get.return_value = self.group
+ self.projects_mock.get.return_value = self.project
+ self.role_assignments_mock.list.return_value = [self.role_assignment]
# Get the command object to test
self.cmd = user.ListUser(self.app, None)
@@ -591,10 +528,10 @@ class TestUserList(TestUser):
def test_user_list_domain(self):
arglist = [
- '--domain', identity_fakes.domain_id,
+ '--domain', self.domain.id,
]
verifylist = [
- ('domain', identity_fakes.domain_id),
+ ('domain', self.domain.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -605,7 +542,7 @@ class TestUserList(TestUser):
# Set expected values
kwargs = {
- 'domain': identity_fakes.domain_id,
+ 'domain': self.domain.id,
'group': None,
}
@@ -618,10 +555,10 @@ class TestUserList(TestUser):
def test_user_list_group(self):
arglist = [
- '--group', identity_fakes.group_name,
+ '--group', self.group.name,
]
verifylist = [
- ('group', identity_fakes.group_name),
+ ('group', self.group.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -633,7 +570,7 @@ class TestUserList(TestUser):
# Set expected values
kwargs = {
'domain': None,
- 'group': identity_fakes.group_id,
+ 'group': self.group.id,
}
self.users_mock.list.assert_called_with(
@@ -679,12 +616,12 @@ class TestUserList(TestUser):
self.assertEqual(collist, columns)
datalist = (
(
- identity_fakes.user_id,
- identity_fakes.user_name,
- identity_fakes.project_id,
- identity_fakes.domain_id,
+ self.user.id,
+ self.user.name,
+ self.project.id,
+ self.domain.id,
'',
- identity_fakes.user_email,
+ self.user.email,
True,
),
)
@@ -692,10 +629,10 @@ class TestUserList(TestUser):
def test_user_list_project(self):
arglist = [
- '--project', identity_fakes.project_name,
+ '--project', self.project.name,
]
verifylist = [
- ('project', identity_fakes.project_name),
+ ('project', self.project.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -705,11 +642,11 @@ class TestUserList(TestUser):
columns, data = self.cmd.take_action(parsed_args)
kwargs = {
- 'project': identity_fakes.project_id,
+ 'project': self.project.id,
}
self.role_assignments_mock.list.assert_called_with(**kwargs)
- self.users_mock.get.assert_called_with(identity_fakes.user_id)
+ self.users_mock.get.assert_called_with(self.user.id)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data))
@@ -717,32 +654,24 @@ class TestUserList(TestUser):
class TestUserSet(TestUser):
+ project = identity_fakes.FakeProject.create_one_project()
+ user = identity_fakes.FakeUser.create_one_user(
+ attrs={'default_project_id': project.id}
+ )
+
def setUp(self):
super(TestUserSet, self).setUp()
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
-
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
- self.users_mock.update.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
+ self.users_mock.get.return_value = self.user
+ self.users_mock.update.return_value = self.user
# Get the command object to test
self.cmd = user.SetUser(self.app, None)
def test_user_set_no_options(self):
arglist = [
- identity_fakes.user_name,
+ self.user.name,
]
verifylist = [
('name', None),
@@ -751,7 +680,7 @@ class TestUserSet(TestUser):
('project', None),
('enable', False),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -762,7 +691,7 @@ class TestUserSet(TestUser):
def test_user_set_name(self):
arglist = [
'--name', 'qwerty',
- identity_fakes.user_name,
+ self.user.name,
]
verifylist = [
('name', 'qwerty'),
@@ -771,7 +700,7 @@ class TestUserSet(TestUser):
('project', None),
('enable', False),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -785,7 +714,7 @@ class TestUserSet(TestUser):
# UserManager.update(user, name=, domain=, project=, password=,
# email=, description=, enabled=, default_project=)
self.users_mock.update.assert_called_with(
- identity_fakes.user_id,
+ self.user.id,
**kwargs
)
self.assertIsNone(result)
@@ -793,7 +722,7 @@ class TestUserSet(TestUser):
def test_user_set_password(self):
arglist = [
'--password', 'secret',
- identity_fakes.user_name,
+ self.user.name,
]
verifylist = [
('name', None),
@@ -803,7 +732,7 @@ class TestUserSet(TestUser):
('project', None),
('enable', False),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -817,7 +746,7 @@ class TestUserSet(TestUser):
# UserManager.update(user, name=, domain=, project=, password=,
# email=, description=, enabled=, default_project=)
self.users_mock.update.assert_called_with(
- identity_fakes.user_id,
+ self.user.id,
**kwargs
)
self.assertIsNone(result)
@@ -825,7 +754,7 @@ class TestUserSet(TestUser):
def test_user_set_password_prompt(self):
arglist = [
'--password-prompt',
- identity_fakes.user_name,
+ self.user.name,
]
verifylist = [
('name', None),
@@ -835,13 +764,13 @@ class TestUserSet(TestUser):
('project', None),
('enable', False),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
mocker = mock.Mock()
mocker.return_value = 'abc123'
- with mock.patch("openstackclient.common.utils.get_password", mocker):
+ with mock.patch("osc_lib.utils.get_password", mocker):
result = self.cmd.take_action(parsed_args)
# Set expected values
@@ -852,7 +781,7 @@ class TestUserSet(TestUser):
# UserManager.update(user, name=, domain=, project=, password=,
# email=, description=, enabled=, default_project=)
self.users_mock.update.assert_called_with(
- identity_fakes.user_id,
+ self.user.id,
**kwargs
)
self.assertIsNone(result)
@@ -860,7 +789,7 @@ class TestUserSet(TestUser):
def test_user_set_email(self):
arglist = [
'--email', 'barney@example.com',
- identity_fakes.user_name,
+ self.user.name,
]
verifylist = [
('name', None),
@@ -869,7 +798,7 @@ class TestUserSet(TestUser):
('project', None),
('enable', False),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -883,24 +812,24 @@ class TestUserSet(TestUser):
# UserManager.update(user, name=, domain=, project=, password=,
# email=, description=, enabled=, default_project=)
self.users_mock.update.assert_called_with(
- identity_fakes.user_id,
+ self.user.id,
**kwargs
)
self.assertIsNone(result)
def test_user_set_project(self):
arglist = [
- '--project', identity_fakes.project_id,
- identity_fakes.user_name,
+ '--project', self.project.id,
+ self.user.name,
]
verifylist = [
('name', None),
('password', None),
('email', None),
- ('project', identity_fakes.project_id),
+ ('project', self.project.id),
('enable', False),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -909,31 +838,31 @@ class TestUserSet(TestUser):
# Set expected values
kwargs = {
'enabled': True,
- 'default_project': identity_fakes.project_id,
+ 'default_project': self.project.id,
}
# UserManager.update(user, name=, domain=, project=, password=,
# email=, description=, enabled=, default_project=)
self.users_mock.update.assert_called_with(
- identity_fakes.user_id,
+ self.user.id,
**kwargs
)
self.assertIsNone(result)
def test_user_set_project_domain(self):
arglist = [
- '--project', identity_fakes.project_id,
- '--project-domain', identity_fakes.domain_id,
- identity_fakes.user_name,
+ '--project', self.project.id,
+ '--project-domain', self.project.domain_id,
+ self.user.name,
]
verifylist = [
('name', None),
('password', None),
('email', None),
- ('project', identity_fakes.project_id),
- ('project_domain', identity_fakes.domain_id),
+ ('project', self.project.id),
+ ('project_domain', self.project.domain_id),
('enable', False),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -942,12 +871,12 @@ class TestUserSet(TestUser):
# Set expected values
kwargs = {
'enabled': True,
- 'default_project': identity_fakes.project_id,
+ 'default_project': self.project.id,
}
# UserManager.update(user, name=, domain=, project=, password=,
# email=, description=, enabled=, default_project=)
self.users_mock.update.assert_called_with(
- identity_fakes.user_id,
+ self.user.id,
**kwargs
)
self.assertIsNone(result)
@@ -955,7 +884,7 @@ class TestUserSet(TestUser):
def test_user_set_enable(self):
arglist = [
'--enable',
- identity_fakes.user_name,
+ self.user.name,
]
verifylist = [
('name', None),
@@ -964,7 +893,7 @@ class TestUserSet(TestUser):
('project', None),
('enable', True),
('disable', False),
- ('user', identity_fakes.user_name),
+ ('user', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -977,7 +906,7 @@ class TestUserSet(TestUser):
# UserManager.update(user, name=, domain=, project=, password=,
# email=, description=, enabled=, default_project=)
self.users_mock.update.assert_called_with(
- identity_fakes.user_id,
+ self.user.id,
**kwargs
)
self.assertIsNone(result)
@@ -985,7 +914,7 @@ class TestUserSet(TestUser):
def test_user_set_disable(self):
arglist = [
'--disable',
- identity_fakes.user_name,
+ self.user.name,
]
verifylist = [
('name', None),
@@ -994,7 +923,7 @@ class TestUserSet(TestUser):
('project', None),
('enable', False),
('disable', True),
- ('user', identity_fakes.user_name),
+ ('user', self.user.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -1007,7 +936,7 @@ class TestUserSet(TestUser):
# UserManager.update(user, name=, domain=, project=, password=,
# email=, description=, enabled=, default_project=)
self.users_mock.update.assert_called_with(
- identity_fakes.user_id,
+ self.user.id,
**kwargs
)
self.assertIsNone(result)
@@ -1023,7 +952,7 @@ class TestUserSetPassword(TestUser):
@contextlib.contextmanager
def _mock_get_password(*passwords):
mocker = mock.Mock(side_effect=passwords)
- with mock.patch("openstackclient.common.utils.get_password", mocker):
+ with mock.patch("osc_lib.utils.get_password", mocker):
yield
def test_user_password_change(self):
@@ -1083,24 +1012,33 @@ class TestUserSetPassword(TestUser):
class TestUserShow(TestUser):
+ user = identity_fakes.FakeUser.create_one_user()
+
def setUp(self):
super(TestUserShow, self).setUp()
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.users_mock.get.return_value = self.user
# Get the command object to test
self.cmd = user.ShowUser(self.app, None)
+ self.app.client_manager.identity.auth.client.get_user_id.\
+ return_value = self.user.id
+ self.app.client_manager.identity.tokens.get_token_data.return_value = \
+ {'token':
+ {'user':
+ {'domain': {},
+ 'id': self.user.id,
+ 'name': self.user.name,
+ }
+ }
+ }
def test_user_show(self):
arglist = [
- identity_fakes.user_id,
+ self.user.id,
]
verifylist = [
- ('user', identity_fakes.user_id),
+ ('user', self.user.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -1109,17 +1047,17 @@ class TestUserShow(TestUser):
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
- self.users_mock.get.assert_called_with(identity_fakes.user_id)
+ self.users_mock.get.assert_called_with(self.user.id)
collist = ('default_project_id', 'domain_id', 'email',
'enabled', 'id', 'name')
self.assertEqual(collist, columns)
datalist = (
- identity_fakes.project_id,
- identity_fakes.domain_id,
- identity_fakes.user_email,
+ self.user.default_project_id,
+ self.user.domain_id,
+ self.user.email,
True,
- identity_fakes.user_id,
- identity_fakes.user_name,
+ self.user.id,
+ self.user.name,
)
self.assertEqual(datalist, data)
diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py
index 018e119..cf08d13 100644
--- a/openstackclient/tests/image/v1/test_image.py
+++ b/openstackclient/tests/image/v1/test_image.py
@@ -16,7 +16,8 @@
import copy
import mock
-from openstackclient.common import exceptions
+from osc_lib import exceptions
+
from openstackclient.image.v1 import image
from openstackclient.tests import fakes
from openstackclient.tests.image.v1 import fakes as image_fakes
@@ -362,6 +363,7 @@ class TestImageList(TestImage):
'Disk Format',
'Container Format',
'Size',
+ 'Checksum',
'Status',
'Visibility',
'Protected',
@@ -377,6 +379,7 @@ class TestImageList(TestImage):
'',
'',
'',
+ '',
'public',
False,
image_fakes.image_owner,
@@ -416,7 +419,7 @@ class TestImageList(TestImage):
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data))
- @mock.patch('openstackclient.common.utils.sort_items')
+ @mock.patch('osc_lib.utils.sort_items')
def test_image_list_sort_option(self, si_mock):
si_mock.side_effect = [
[copy.deepcopy(image_fakes.IMAGE)], [],
@@ -474,8 +477,8 @@ class TestImageSet(TestImage):
result = self.cmd.take_action(parsed_args)
- # Verify update() was not called, if it was show the args
- self.assertEqual(self.images_mock.update.call_args_list, [])
+ self.images_mock.update.assert_called_with(image_fakes.image_id,
+ **{})
self.assertIsNone(result)
def test_image_set_options(self):
diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py
index 24aaec5..d450dec 100644
--- a/openstackclient/tests/image/v2/fakes.py
+++ b/openstackclient/tests/image/v2/fakes.py
@@ -19,13 +19,12 @@ import random
import uuid
from glanceclient.v2 import schemas
+from osc_lib import utils as common_utils
import warlock
-from openstackclient.common import utils as common_utils
from openstackclient.tests import fakes
-from openstackclient.tests import utils
-
from openstackclient.tests.identity.v3 import fakes as identity_fakes
+from openstackclient.tests import utils
image_id = '0f41529e-7c12-4de8-be2d-181abb825b3c'
image_name = 'graven'
@@ -50,13 +49,6 @@ IMAGE_SHOW = copy.copy(IMAGE)
IMAGE_SHOW['tags'] = ''
IMAGE_SHOW_data = tuple((IMAGE_SHOW[x] for x in sorted(IMAGE_SHOW)))
-member_status = 'pending'
-MEMBER = {
- 'member_id': identity_fakes.project_id,
- 'image_id': image_id,
- 'status': member_status,
-}
-
# Just enough v2 schema to do some testing
IMAGE_schema = {
"additionalProperties": {
@@ -191,7 +183,7 @@ class FakeImage(object):
:param Dictionary attrs:
A dictionary with all attrbutes of image
- :retrun:
+ :return:
A FakeResource object with id, name, owner, protected,
visibility and tags attrs
"""
@@ -289,3 +281,29 @@ class FakeImage(object):
else:
data_list.append(getattr(image, x))
return tuple(data_list)
+
+ @staticmethod
+ def create_one_image_member(attrs=None):
+ """Create a fake image member.
+
+ :param Dictionary attrs:
+ A dictionary with all attrbutes of image member
+ :return:
+ A FakeResource object with member_id, image_id and so on
+ """
+ attrs = attrs or {}
+
+ # Set default attribute
+ image_member_info = {
+ 'member_id': 'member-id-' + uuid.uuid4().hex,
+ 'image_id': 'image-id-' + uuid.uuid4().hex,
+ 'status': 'pending',
+ }
+
+ # Overwrite default attributes if there are some attributes set
+ image_member_info.update(attrs)
+
+ image_member = fakes.FakeModel(
+ copy.deepcopy(image_member_info))
+
+ return image_member
diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py
index ca20d83..2b116b4 100644
--- a/openstackclient/tests/image/v2/test_image.py
+++ b/openstackclient/tests/image/v2/test_image.py
@@ -16,13 +16,12 @@
import copy
import mock
+from glanceclient.v2 import schemas
+from osc_lib import exceptions
+from osc_lib import utils as common_utils
import warlock
-from glanceclient.v2 import schemas
-from openstackclient.common import exceptions
-from openstackclient.common import utils as common_utils
from openstackclient.image.v2 import image
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
@@ -57,23 +56,18 @@ class TestImage(image_fakes.TestImagev2):
class TestImageCreate(TestImage):
+ project = identity_fakes.FakeProject.create_one_project()
+ domain = identity_fakes.FakeDomain.create_one_domain()
+
def setUp(self):
super(TestImageCreate, self).setUp()
self.new_image = image_fakes.FakeImage.create_one_image()
self.images_mock.create.return_value = self.new_image
- self.project_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.project_mock.get.return_value = self.project
- self.domain_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
+ self.domain_mock.get.return_value = self.domain
# This is the return value for utils.find_resource()
self.images_mock.get.return_value = copy.deepcopy(
@@ -144,7 +138,7 @@ class TestImageCreate(TestImage):
('--private'
if self.new_image.visibility == 'private' else '--public'),
'--project', self.new_image.owner,
- '--project-domain', identity_fakes.domain_id,
+ '--project-domain', self.domain.id,
self.new_image.name,
]
verifylist = [
@@ -157,7 +151,7 @@ class TestImageCreate(TestImage):
('public', self.new_image.visibility == 'public'),
('private', self.new_image.visibility == 'private'),
('project', self.new_image.owner),
- ('project_domain', identity_fakes.domain_id),
+ ('project_domain', self.domain.id),
('name', self.new_image.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -174,7 +168,7 @@ class TestImageCreate(TestImage):
disk_format='fs',
min_disk=10,
min_ram=4,
- owner=identity_fakes.project_id,
+ owner=self.project.id,
protected=self.new_image.protected,
visibility=self.new_image.visibility,
)
@@ -345,7 +339,13 @@ class TestImageCreate(TestImage):
class TestAddProjectToImage(TestImage):
+ project = identity_fakes.FakeProject.create_one_project()
+ domain = identity_fakes.FakeDomain.create_one_domain()
_image = image_fakes.FakeImage.create_one_image()
+ new_member = image_fakes.FakeImage.create_one_image_member(
+ attrs={'image_id': _image.id,
+ 'member_id': project.id}
+ )
columns = (
'image_id',
@@ -355,8 +355,8 @@ class TestAddProjectToImage(TestImage):
datalist = (
_image.id,
- identity_fakes.project_id,
- image_fakes.member_status
+ new_member.member_id,
+ new_member.status,
)
def setUp(self):
@@ -366,32 +366,20 @@ class TestAddProjectToImage(TestImage):
self.images_mock.get.return_value = self._image
# Update the image_id in the MEMBER dict
- self.new_member = copy.deepcopy(image_fakes.MEMBER)
- self.new_member['image_id'] = self._image.id
- self.image_members_mock.create.return_value = fakes.FakeModel(
- self.new_member,
- )
- self.project_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
- self.domain_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
+ self.image_members_mock.create.return_value = self.new_member
+ self.project_mock.get.return_value = self.project
+ self.domain_mock.get.return_value = self.domain
# Get the command object to test
self.cmd = image.AddProjectToImage(self.app, None)
def test_add_project_to_image_no_option(self):
arglist = [
self._image.id,
- identity_fakes.project_id,
+ self.project.id,
]
verifylist = [
('image', self._image.id),
- ('project', identity_fakes.project_id),
+ ('project', self.project.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -401,7 +389,7 @@ class TestAddProjectToImage(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.image_members_mock.create.assert_called_with(
self._image.id,
- identity_fakes.project_id
+ self.project.id
)
self.assertEqual(self.columns, columns)
@@ -410,13 +398,13 @@ class TestAddProjectToImage(TestImage):
def test_add_project_to_image_with_option(self):
arglist = [
self._image.id,
- identity_fakes.project_id,
- '--project-domain', identity_fakes.domain_id,
+ self.project.id,
+ '--project-domain', self.domain.id,
]
verifylist = [
('image', self._image.id),
- ('project', identity_fakes.project_id),
- ('project_domain', identity_fakes.domain_id),
+ ('project', self.project.id),
+ ('project_domain', self.domain.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -426,7 +414,7 @@ class TestAddProjectToImage(TestImage):
columns, data = self.cmd.take_action(parsed_args)
self.image_members_mock.create.assert_called_with(
self._image.id,
- identity_fakes.project_id
+ self.project.id
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, data)
@@ -473,6 +461,37 @@ class TestImageDelete(TestImage):
self.images_mock.delete.assert_has_calls(calls)
self.assertIsNone(result)
+ def test_image_delete_multi_images_exception(self):
+
+ images = image_fakes.FakeImage.create_images(count=2)
+ arglist = [
+ images[0].id,
+ images[1].id,
+ 'x-y-x',
+ ]
+ verifylist = [
+ ('images', arglist)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # Fake exception in utils.find_resource()
+ # In image v2, we use utils.find_resource() to find a network.
+ # It calls get() several times, but find() only one time. So we
+ # choose to fake get() always raise exception, then pass through.
+ # And fake find() to find the real network or not.
+ ret_find = [
+ images[0],
+ images[1],
+ exceptions.NotFound('404'),
+ ]
+
+ self.images_mock.get = Exception()
+ self.images_mock.find.side_effect = ret_find
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+ calls = [mock.call(i.id) for i in images]
+ self.images_mock.delete.assert_has_calls(calls)
+
class TestImageList(TestImage):
@@ -611,6 +630,7 @@ class TestImageList(TestImage):
'Disk Format',
'Container Format',
'Size',
+ 'Checksum',
'Status',
'Visibility',
'Protected',
@@ -626,6 +646,7 @@ class TestImageList(TestImage):
'',
'',
'',
+ '',
self._image.visibility,
self._image.protected,
self._image.owner,
@@ -660,7 +681,7 @@ class TestImageList(TestImage):
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data))
- @mock.patch('openstackclient.common.utils.sort_items')
+ @mock.patch('osc_lib.utils.sort_items')
def test_image_list_sort_option(self, si_mock):
si_mock.return_value = [copy.deepcopy(self._image)]
@@ -697,7 +718,7 @@ class TestImageList(TestImage):
self.assertEqual(self.columns, columns)
self.assertEqual(len(self.datalist), len(tuple(data)))
- @mock.patch('openstackclient.common.utils.find_resource')
+ @mock.patch('osc_lib.utils.find_resource')
def test_image_list_marker_option(self, fr_mock):
# tangchen: Since image_fakes.IMAGE is a dict, it cannot offer a .id
# operation. Will fix this by using FakeImage class instead
@@ -721,6 +742,9 @@ class TestImageList(TestImage):
class TestRemoveProjectImage(TestImage):
+ project = identity_fakes.FakeProject.create_one_project()
+ domain = identity_fakes.FakeDomain.create_one_domain()
+
def setUp(self):
super(TestRemoveProjectImage, self).setUp()
@@ -728,16 +752,8 @@ class TestRemoveProjectImage(TestImage):
# This is the return value for utils.find_resource()
self.images_mock.get.return_value = self._image
- self.project_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
- self.domain_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
+ self.project_mock.get.return_value = self.project
+ self.domain_mock.get.return_value = self.domain
self.image_members_mock.delete.return_value = None
# Get the command object to test
self.cmd = image.RemoveProjectImage(self.app, None)
@@ -745,11 +761,11 @@ class TestRemoveProjectImage(TestImage):
def test_remove_project_image_no_options(self):
arglist = [
self._image.id,
- identity_fakes.project_id,
+ self.project.id,
]
verifylist = [
('image', self._image.id),
- ('project', identity_fakes.project_id),
+ ('project', self.project.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -757,20 +773,20 @@ class TestRemoveProjectImage(TestImage):
self.image_members_mock.delete.assert_called_with(
self._image.id,
- identity_fakes.project_id,
+ self.project.id,
)
self.assertIsNone(result)
def test_remove_project_image_with_options(self):
arglist = [
self._image.id,
- identity_fakes.project_id,
- '--project-domain', identity_fakes.domain_id,
+ self.project.id,
+ '--project-domain', self.domain.id,
]
verifylist = [
('image', self._image.id),
- ('project', identity_fakes.project_id),
- ('project_domain', identity_fakes.domain_id),
+ ('project', self.project.id),
+ ('project_domain', self.domain.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -778,13 +794,16 @@ class TestRemoveProjectImage(TestImage):
self.image_members_mock.delete.assert_called_with(
self._image.id,
- identity_fakes.project_id,
+ self.project.id,
)
self.assertIsNone(result)
class TestImageSet(TestImage):
+ project = identity_fakes.FakeProject.create_one_project()
+ domain = identity_fakes.FakeDomain.create_one_domain()
+
def setUp(self):
super(TestImageSet, self).setUp()
# Set up the schema
@@ -793,23 +812,28 @@ class TestImageSet(TestImage):
schemas.SchemaBasedModel,
)
- self.project_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.project_mock.get.return_value = self.project
- self.domain_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
+ self.domain_mock.get.return_value = self.domain
self.images_mock.get.return_value = self.model(**image_fakes.IMAGE)
self.images_mock.update.return_value = self.model(**image_fakes.IMAGE)
# Get the command object to test
self.cmd = image.SetImage(self.app, None)
+ def test_image_set_no_options(self):
+ arglist = [
+ image_fakes.image_id,
+ ]
+ verifylist = [
+ ('image', image_fakes.image_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.assertIsNone(result)
+
def test_image_set_options(self):
arglist = [
'--name', 'new-name',
@@ -817,8 +841,8 @@ class TestImageSet(TestImage):
'--min-ram', '4',
'--container-format', 'ovf',
'--disk-format', 'vmdk',
- '--project', identity_fakes.project_name,
- '--project-domain', identity_fakes.domain_id,
+ '--project', self.project.name,
+ '--project-domain', self.domain.id,
image_fakes.image_id,
]
verifylist = [
@@ -827,8 +851,8 @@ class TestImageSet(TestImage):
('min_ram', 4),
('container_format', 'ovf'),
('disk_format', 'vmdk'),
- ('project', identity_fakes.project_name),
- ('project_domain', identity_fakes.domain_id),
+ ('project', self.project.name),
+ ('project_domain', self.domain.id),
('image', image_fakes.image_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -837,7 +861,7 @@ class TestImageSet(TestImage):
kwargs = {
'name': 'new-name',
- 'owner': identity_fakes.project_id,
+ 'owner': self.project.id,
'min_disk': 2,
'min_ram': 4,
'container_format': 'ovf',
@@ -1210,6 +1234,19 @@ class TestImageUnset(TestImage):
# Get the command object to test
self.cmd = image.UnsetImage(self.app, None)
+ def test_image_unset_no_options(self):
+ arglist = [
+ image_fakes.image_id,
+ ]
+ verifylist = [
+ ('image', image_fakes.image_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.assertIsNone(result)
+
def test_image_unset_tag_option(self):
arglist = [
diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py
index ccbe395..a6de75e 100644
--- a/openstackclient/tests/network/v2/fakes.py
+++ b/openstackclient/tests/network/v2/fakes.py
@@ -17,14 +17,9 @@ import mock
import uuid
from openstackclient.tests import fakes
+from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3
from openstackclient.tests import utils
-extension_name = 'Matrix'
-extension_namespace = 'http://docs.openstack.org/network/'
-extension_description = 'Simulated reality'
-extension_updated = '2013-07-09T12:00:0-00:00'
-extension_alias = 'Dystopian'
-extension_links = '[{"href":''"https://github.com/os/network", "type"}]'
QUOTA = {
"subnet": 10,
@@ -42,21 +37,11 @@ QUOTA = {
}
-def create_extension():
- extension = mock.Mock()
- extension.name = extension_name
- extension.namespace = extension_namespace
- extension.description = extension_description
- extension.updated = extension_updated
- extension.alias = extension_alias
- extension.links = extension_links
- return extension
-
-
class FakeNetworkV2Client(object):
def __init__(self, **kwargs):
- self.extensions = mock.Mock(return_value=[create_extension()])
+ self.extensions = mock.Mock()
+ self.extensions.resource_class = fakes.FakeResource(None, {})
class TestNetworkV2(utils.TestCommand):
@@ -73,6 +58,13 @@ class TestNetworkV2(utils.TestCommand):
token=fakes.AUTH_TOKEN,
)
+ self.app.client_manager.identity = (
+ identity_fakes_v3.FakeIdentityv3Client(
+ endpoint=fakes.AUTH_URL,
+ token=fakes.AUTH_TOKEN,
+ )
+ )
+
class FakeAddressScope(object):
"""Fake one or more address scopes."""
@@ -240,6 +232,39 @@ class FakeIPAvailability(object):
return network_ip_availabilities
+class FakeExtension(object):
+ """Fake one or more extension."""
+
+ @staticmethod
+ def create_one_extension(attrs=None):
+ """Create a fake extension.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object with name, namespace, etc.
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ extension_info = {
+ 'name': 'name-' + uuid.uuid4().hex,
+ 'namespace': 'http://docs.openstack.org/network/',
+ 'description': 'description-' + uuid.uuid4().hex,
+ 'updated': '2013-07-09T12:00:0-00:00',
+ 'alias': 'Dystopian',
+ 'links': '[{"href":''"https://github.com/os/network", "type"}]',
+ }
+
+ # Overwrite default attributes.
+ extension_info.update(attrs)
+
+ extension = fakes.FakeResource(
+ info=copy.deepcopy(extension_info),
+ loaded=True)
+ return extension
+
+
class FakeNetwork(object):
"""Fake one or more networks."""
@@ -268,6 +293,7 @@ class FakeNetwork(object):
'availability_zones': [],
'availability_zone_hints': [],
'is_default': False,
+ 'port_security_enabled': True,
}
# Overwrite default attributes.
@@ -279,6 +305,8 @@ class FakeNetwork(object):
# Set attributes with special mapping in OpenStack SDK.
network.project_id = network_attrs['tenant_id']
network.is_router_external = network_attrs['router:external']
+ network.is_port_security_enabled = \
+ network_attrs['port_security_enabled']
return network
@@ -335,7 +363,7 @@ class FakeNetworkSegment(object):
# Set default attributes.
network_segment_attrs = {
- 'id': 'segment-id-' + uuid.uuid4().hex,
+ 'id': 'network-segment-id-' + uuid.uuid4().hex,
'network_id': 'network-id-' + uuid.uuid4().hex,
'network_type': 'vlan',
'physical_network': 'physical-network-name-' + uuid.uuid4().hex,
@@ -463,6 +491,143 @@ class FakePort(object):
return mock.MagicMock(side_effect=ports)
+class FakeNetworkAgent(object):
+ """Fake one or more network agents."""
+
+ @staticmethod
+ def create_one_network_agent(attrs=None):
+ """Create a fake network agent
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, agent_type, and so on.
+ """
+ attrs = attrs or {}
+
+ # Set default attributes
+ agent_attrs = {
+ 'id': 'agent-id-' + uuid.uuid4().hex,
+ 'agent_type': 'agent-type-' + uuid.uuid4().hex,
+ 'host': 'host-' + uuid.uuid4().hex,
+ 'availability_zone': 'zone-' + uuid.uuid4().hex,
+ 'alive': True,
+ 'admin_state_up': True,
+ 'binary': 'binary-' + uuid.uuid4().hex,
+ 'configurations': {'subnet': 2, 'networks': 1},
+ }
+ agent_attrs.update(attrs)
+ agent = fakes.FakeResource(info=copy.deepcopy(agent_attrs),
+ loaded=True)
+ return agent
+
+ @staticmethod
+ def create_network_agents(attrs=None, count=2):
+ """Create multiple fake network agents.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of network agents to fake
+ :return:
+ A list of FakeResource objects faking the network agents
+ """
+ agents = []
+ for i in range(0, count):
+ agents.append(FakeNetworkAgent.create_one_network_agent(attrs))
+
+ return agents
+
+ @staticmethod
+ def get_network_agents(agents=None, count=2):
+ """Get an iterable MagicMock object with a list of faked network agents.
+
+ If network agents list is provided, then initialize the Mock object
+ with the list. Otherwise create one.
+
+ :param List agents:
+ A list of FakeResource objects faking network agents
+ :param int count:
+ The number of network agents to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ network agents
+ """
+ if agents is None:
+ agents = FakeNetworkAgent.create_network_agents(count)
+ return mock.MagicMock(side_effect=agents)
+
+
+class FakeNetworkRBAC(object):
+ """Fake one or more network rbac policies."""
+
+ @staticmethod
+ def create_one_network_rbac(attrs=None):
+ """Create a fake network rbac
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, action, target_tenant,
+ tenant_id, type
+ """
+ attrs = attrs or {}
+
+ # Set default attributes
+ rbac_attrs = {
+ 'id': 'rbac-id-' + uuid.uuid4().hex,
+ 'object_type': 'network',
+ 'object_id': 'object-id-' + uuid.uuid4().hex,
+ 'action': 'access_as_shared',
+ 'target_tenant': 'target-tenant-' + uuid.uuid4().hex,
+ 'tenant_id': 'tenant-id-' + uuid.uuid4().hex,
+ }
+ rbac_attrs.update(attrs)
+ rbac = fakes.FakeResource(info=copy.deepcopy(rbac_attrs),
+ loaded=True)
+ # Set attributes with special mapping in OpenStack SDK.
+ rbac.project_id = rbac_attrs['tenant_id']
+ rbac.target_project_id = rbac_attrs['target_tenant']
+ return rbac
+
+ @staticmethod
+ def create_network_rbacs(attrs=None, count=2):
+ """Create multiple fake network rbac policies.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of rbac policies to fake
+ :return:
+ A list of FakeResource objects faking the rbac policies
+ """
+ rbac_policies = []
+ for i in range(0, count):
+ rbac_policies.append(FakeNetworkRBAC.
+ create_one_network_rbac(attrs))
+
+ return rbac_policies
+
+ @staticmethod
+ def get_network_rbacs(rbac_policies=None, count=2):
+ """Get an iterable MagicMock object with a list of faked rbac policies.
+
+ If rbac policies list is provided, then initialize the Mock object
+ with the list. Otherwise create one.
+
+ :param List rbac_policies:
+ A list of FakeResource objects faking rbac policies
+ :param int count:
+ The number of rbac policies to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ rbac policies
+ """
+ if rbac_policies is None:
+ rbac_policies = FakeNetworkRBAC.create_network_rbacs(count)
+ return mock.MagicMock(side_effect=rbac_policies)
+
+
class FakeRouter(object):
"""Fake one or more routers."""
@@ -594,6 +759,25 @@ class FakeSecurityGroup(object):
return security_groups
+ @staticmethod
+ def get_security_groups(security_groups=None, count=2):
+ """Get an iterable MagicMock object with a list of faked security groups.
+
+ If security groups list is provided, then initialize the Mock object
+ with the list. Otherwise create one.
+
+ :param List security groups:
+ A list of FakeResource objects faking security groups
+ :param int count:
+ The number of security groups to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ security groups
+ """
+ if security_groups is None:
+ security_groups = FakeSecurityGroup.create_security_groups(count)
+ return mock.MagicMock(side_effect=security_groups)
+
class FakeSecurityGroupRule(object):
"""Fake one or more security group rules."""
@@ -653,6 +837,26 @@ class FakeSecurityGroupRule(object):
return security_group_rules
+ @staticmethod
+ def get_security_group_rules(security_group_rules=None, count=2):
+ """Get an iterable MagicMock object with a list of faked security group rules.
+
+ If security group rules list is provided, then initialize the Mock
+ object with the list. Otherwise create one.
+
+ :param List security group rules:
+ A list of FakeResource objects faking security group rules
+ :param int count:
+ The number of security group rules to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ security group rules
+ """
+ if security_group_rules is None:
+ security_group_rules = (
+ FakeSecurityGroupRule.create_security_group_rules(count))
+ return mock.MagicMock(side_effect=security_group_rules)
+
class FakeSubnet(object):
"""Fake one or more subnets."""
@@ -682,9 +886,10 @@ class FakeSubnet(object):
'host_routes': [],
'ip_version': 4,
'gateway_ip': '10.10.10.1',
- 'ipv6_address_mode': 'None',
- 'ipv6_ra_mode': 'None',
- 'subnetpool_id': 'None',
+ 'ipv6_address_mode': None,
+ 'ipv6_ra_mode': None,
+ 'segment_id': None,
+ 'subnetpool_id': None,
}
# Overwrite default attributes.
@@ -715,6 +920,25 @@ class FakeSubnet(object):
return subnets
+ @staticmethod
+ def get_subnets(subnets=None, count=2):
+ """Get an iterable MagicMock object with a list of faked subnets.
+
+ If subnets list is provided, then initialize the Mock object
+ with the list. Otherwise create one.
+
+ :param List subnets:
+ A list of FakeResource objects faking subnets
+ :param int count:
+ The number of subnets to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ subnets
+ """
+ if subnets is None:
+ subnets = FakeSubnet.create_subnets(count)
+ return mock.MagicMock(side_effect=subnets)
+
class FakeFloatingIP(object):
"""Fake one or more floating ip."""
@@ -854,3 +1078,22 @@ class FakeSubnetPool(object):
)
return subnet_pools
+
+ @staticmethod
+ def get_subnet_pools(subnet_pools=None, count=2):
+ """Get an iterable MagicMock object with a list of faked subnet pools.
+
+ If subnet_pools list is provided, then initialize the Mock object
+ with the list. Otherwise create one.
+
+ :param List subnet pools:
+ A list of FakeResource objects faking subnet pools
+ :param int count:
+ The number of subnet pools to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ subnet pools
+ """
+ if subnet_pools is None:
+ subnet_pools = FakeSubnetPool.create_subnet_pools(count)
+ return mock.MagicMock(side_effect=subnet_pools)
diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py
index b4f4fa8..342cf49 100644
--- a/openstackclient/tests/network/v2/test_address_scope.py
+++ b/openstackclient/tests/network/v2/test_address_scope.py
@@ -11,13 +11,12 @@
# under the License.
#
-import copy
import mock
-
from mock import call
-from openstackclient.common import exceptions
+
+from osc_lib import exceptions
+
from openstackclient.network.v2 import address_scope
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3
from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils as tests_utils
@@ -30,15 +29,21 @@ class TestAddressScope(network_fakes.TestNetworkV2):
# Get a shortcut to the network client
self.network = self.app.client_manager.network
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+ # Get a shortcut to the DomainManager Mock
+ self.domains_mock = self.app.client_manager.identity.domains
class TestCreateAddressScope(TestAddressScope):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ domain = identity_fakes_v3.FakeDomain.create_one_domain()
# The new address scope created.
new_address_scope = (
network_fakes.FakeAddressScope.create_one_address_scope(
attrs={
- 'tenant_id': identity_fakes_v3.project_id,
+ 'tenant_id': project.id,
}
))
columns = (
@@ -64,29 +69,8 @@ class TestCreateAddressScope(TestAddressScope):
# Get the command object to test
self.cmd = address_scope.CreateAddressScope(self.app, self.namespace)
- # Set identity client v3. And get a shortcut to Identity client.
- identity_client = identity_fakes_v3.FakeIdentityv3Client(
- endpoint=fakes.AUTH_URL,
- token=fakes.AUTH_TOKEN,
- )
- self.app.client_manager.identity = identity_client
- self.identity = self.app.client_manager.identity
-
- # Get a shortcut to the ProjectManager Mock
- self.projects_mock = self.identity.projects
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes_v3.PROJECT),
- loaded=True,
- )
-
- # Get a shortcut to the DomainManager Mock
- self.domains_mock = self.identity.domains
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes_v3.DOMAIN),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
+ self.domains_mock.get.return_value = self.domain
def test_create_no_options(self):
arglist = []
@@ -120,15 +104,15 @@ class TestCreateAddressScope(TestAddressScope):
arglist = [
'--ip-version', str(self.new_address_scope.ip_version),
'--share',
- '--project', identity_fakes_v3.project_name,
- '--project-domain', identity_fakes_v3.domain_name,
+ '--project', self.project.name,
+ '--project-domain', self.domain.name,
self.new_address_scope.name,
]
verifylist = [
('ip_version', self.new_address_scope.ip_version),
('share', True),
- ('project', identity_fakes_v3.project_name),
- ('project_domain', identity_fakes_v3.domain_name),
+ ('project', self.project.name),
+ ('project_domain', self.domain.name),
('name', self.new_address_scope.name),
]
@@ -138,7 +122,7 @@ class TestCreateAddressScope(TestAddressScope):
self.network.create_address_scope.assert_called_once_with(**{
'ip_version': self.new_address_scope.ip_version,
'shared': True,
- 'tenant_id': identity_fakes_v3.project_id,
+ 'tenant_id': self.project.id,
'name': self.new_address_scope.name,
})
self.assertEqual(self.columns, columns)
@@ -313,8 +297,12 @@ class TestSetAddressScope(TestAddressScope):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.assertRaises(exceptions.CommandError, self.cmd.take_action,
- parsed_args)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {}
+ self.network.update_address_scope.assert_called_with(
+ self._address_scope, **attrs)
+ self.assertIsNone(result)
def test_set_name_and_share(self):
arglist = [
diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py
index f9ccfe1..234fe44 100644
--- a/openstackclient/tests/network/v2/test_floating_ip.py
+++ b/openstackclient/tests/network/v2/test_floating_ip.py
@@ -12,6 +12,9 @@
#
import mock
+from mock import call
+
+from osc_lib import exceptions
from openstackclient.network.v2 import floating_ip
from openstackclient.tests.compute.v2 import fakes as compute_fakes
@@ -140,33 +143,84 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork):
class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork):
- # The floating ip to be deleted.
- floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip()
+ # The floating ips to be deleted.
+ floating_ips = network_fakes.FakeFloatingIP.create_floating_ips(count=2)
def setUp(self):
super(TestDeleteFloatingIPNetwork, self).setUp()
self.network.delete_ip = mock.Mock(return_value=None)
- self.network.find_ip = mock.Mock(return_value=self.floating_ip)
+ self.network.find_ip = (
+ network_fakes.FakeFloatingIP.get_floating_ips(self.floating_ips))
# Get the command object to test
self.cmd = floating_ip.DeleteFloatingIP(self.app, self.namespace)
def test_floating_ip_delete(self):
arglist = [
- self.floating_ip.id,
+ self.floating_ips[0].id,
]
verifylist = [
- ('floating_ip', self.floating_ip.id),
+ ('floating_ip', [self.floating_ips[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.network.find_ip.assert_called_once_with(self.floating_ip.id)
- self.network.delete_ip.assert_called_once_with(self.floating_ip)
+ self.network.find_ip.assert_called_once_with(
+ self.floating_ips[0].id, ignore_missing=False)
+ self.network.delete_ip.assert_called_once_with(self.floating_ips[0])
self.assertIsNone(result)
+ def test_multi_floating_ips_delete(self):
+ arglist = []
+ verifylist = []
+
+ for f in self.floating_ips:
+ arglist.append(f.id)
+ verifylist = [
+ ('floating_ip', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for f in self.floating_ips:
+ calls.append(call(f))
+ self.network.delete_ip.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_multi_floating_ips_delete_with_exception(self):
+ arglist = [
+ self.floating_ips[0].id,
+ 'unexist_floating_ip',
+ ]
+ verifylist = [
+ ('floating_ip',
+ [self.floating_ips[0].id, 'unexist_floating_ip']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self.floating_ips[0], exceptions.CommandError]
+ self.network.find_ip = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 floating_ips failed to delete.', str(e))
+
+ self.network.find_ip.assert_any_call(
+ self.floating_ips[0].id, ignore_missing=False)
+ self.network.find_ip.assert_any_call(
+ 'unexist_floating_ip', ignore_missing=False)
+ self.network.delete_ip.assert_called_once_with(
+ self.floating_ips[0]
+ )
+
class TestListFloatingIPNetwork(TestFloatingIPNetwork):
@@ -335,8 +389,8 @@ class TestCreateFloatingIPCompute(TestFloatingIPCompute):
class TestDeleteFloatingIPCompute(TestFloatingIPCompute):
- # The floating ip to be deleted.
- floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip()
+ # The floating ips to be deleted.
+ floating_ips = compute_fakes.FakeFloatingIP.create_floating_ips(count=2)
def setUp(self):
super(TestDeleteFloatingIPCompute, self).setUp()
@@ -346,27 +400,78 @@ class TestDeleteFloatingIPCompute(TestFloatingIPCompute):
self.compute.floating_ips.delete.return_value = None
# Return value of utils.find_resource()
- self.compute.floating_ips.get.return_value = self.floating_ip
+ self.compute.floating_ips.get = (
+ compute_fakes.FakeFloatingIP.get_floating_ips(self.floating_ips))
# Get the command object to test
self.cmd = floating_ip.DeleteFloatingIP(self.app, None)
def test_floating_ip_delete(self):
arglist = [
- self.floating_ip.id,
+ self.floating_ips[0].id,
]
verifylist = [
- ('floating_ip', self.floating_ip.id),
+ ('floating_ip', [self.floating_ips[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.compute.floating_ips.delete.assert_called_once_with(
- self.floating_ip.id
+ self.floating_ips[0].id
)
self.assertIsNone(result)
+ def test_multi_floating_ips_delete(self):
+ arglist = []
+ verifylist = []
+
+ for f in self.floating_ips:
+ arglist.append(f.id)
+ verifylist = [
+ ('floating_ip', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for f in self.floating_ips:
+ calls.append(call(f.id))
+ self.compute.floating_ips.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_multi_floating_ips_delete_with_exception(self):
+ arglist = [
+ self.floating_ips[0].id,
+ 'unexist_floating_ip',
+ ]
+ verifylist = [
+ ('floating_ip',
+ [self.floating_ips[0].id, 'unexist_floating_ip']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self.floating_ips[0], exceptions.CommandError]
+ self.compute.floating_ips.get = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+ self.compute.floating_ips.find.side_effect = exceptions.NotFound(None)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 floating_ips failed to delete.', str(e))
+
+ self.compute.floating_ips.get.assert_any_call(
+ self.floating_ips[0].id)
+ self.compute.floating_ips.get.assert_any_call(
+ 'unexist_floating_ip')
+ self.compute.floating_ips.delete.assert_called_once_with(
+ self.floating_ips[0].id
+ )
+
class TestListFloatingIPCompute(TestFloatingIPCompute):
diff --git a/openstackclient/tests/network/v2/test_floating_ip_pool.py b/openstackclient/tests/network/v2/test_floating_ip_pool.py
new file mode 100644
index 0000000..22d20d2
--- /dev/null
+++ b/openstackclient/tests/network/v2/test_floating_ip_pool.py
@@ -0,0 +1,97 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from osc_lib import exceptions
+
+from openstackclient.network.v2 import floating_ip_pool
+from openstackclient.tests.compute.v2 import fakes as compute_fakes
+from openstackclient.tests.network.v2 import fakes as network_fakes
+
+
+# Tests for Network API v2
+#
+class TestFloatingIPPoolNetwork(network_fakes.TestNetworkV2):
+
+ def setUp(self):
+ super(TestFloatingIPPoolNetwork, self).setUp()
+
+ # Get a shortcut to the network client
+ self.network = self.app.client_manager.network
+
+
+class TestListFloatingIPPoolNetwork(TestFloatingIPPoolNetwork):
+
+ def setUp(self):
+ super(TestListFloatingIPPoolNetwork, self).setUp()
+
+ # Get the command object to test
+ self.cmd = floating_ip_pool.ListFloatingIPPool(self.app,
+ self.namespace)
+
+ def test_floating_ip_list(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+
+
+# Tests for Compute network
+#
+class TestFloatingIPPoolCompute(compute_fakes.TestComputev2):
+
+ def setUp(self):
+ super(TestFloatingIPPoolCompute, self).setUp()
+
+ # Get a shortcut to the compute client
+ self.compute = self.app.client_manager.compute
+
+
+class TestListFloatingIPPoolCompute(TestFloatingIPPoolCompute):
+
+ # The floating ip pools to list up
+ floating_ip_pools = \
+ compute_fakes.FakeFloatingIPPool.create_floating_ip_pools(count=3)
+
+ columns = (
+ 'Name',
+ )
+
+ data = []
+ for pool in floating_ip_pools:
+ data.append((
+ pool.name,
+ ))
+
+ def setUp(self):
+ super(TestListFloatingIPPoolCompute, self).setUp()
+
+ self.app.client_manager.network_endpoint_enabled = False
+
+ self.compute.floating_ip_pools.list.return_value = \
+ self.floating_ip_pools
+
+ # Get the command object to test
+ self.cmd = floating_ip_pool.ListFloatingIPPool(self.app, None)
+
+ def test_floating_ip_list(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.compute.floating_ip_pools.list.assert_called_once_with()
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
diff --git a/openstackclient/tests/network/v2/test_ip_availability.py b/openstackclient/tests/network/v2/test_ip_availability.py
index 04979e7..f74bf8f 100644
--- a/openstackclient/tests/network/v2/test_ip_availability.py
+++ b/openstackclient/tests/network/v2/test_ip_availability.py
@@ -11,12 +11,11 @@
# under the License.
#
-import copy
import mock
-from openstackclient.common import utils as osc_utils
+from osc_lib import utils as common_utils
+
from openstackclient.network.v2 import ip_availability
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils as tests_utils
@@ -30,21 +29,11 @@ class TestIPAvailability(network_fakes.TestNetworkV2):
# Get a shortcut to the network client
self.network = self.app.client_manager.network
- # Set identity client v3. And get a shortcut to Identity client.
- identity_client = identity_fakes.FakeIdentityv3Client(
- endpoint=fakes.AUTH_URL,
- token=fakes.AUTH_TOKEN,
- )
- self.app.client_manager.identity = identity_client
- self.identity = self.app.client_manager.identity
-
# Get a shortcut to the ProjectManager Mock
- self.projects_mock = self.identity.projects
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock = self.app.client_manager.identity.projects
+
+ self.project = identity_fakes.FakeProject.create_one_project()
+ self.projects_mock.get.return_value = self.project
class TestListIPAvailability(TestIPAvailability):
@@ -81,8 +70,10 @@ class TestListIPAvailability(TestIPAvailability):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
+ filters = {'ip_version': 4}
- self.network.network_ip_availabilities.assert_called_once_with()
+ self.network.network_ip_availabilities.assert_called_once_with(
+ **filters)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
@@ -106,16 +97,17 @@ class TestListIPAvailability(TestIPAvailability):
def test_list_project(self):
arglist = [
- '--project', identity_fakes.project_name
+ '--project', self.project.name
]
verifylist = [
- ('project', identity_fakes.project_name)
+ ('project', self.project.name)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- filters = {'tenant_id': identity_fakes.project_id}
+ filters = {'tenant_id': self.project.id,
+ 'ip_version': 4}
self.network.network_ip_availabilities.assert_called_once_with(
**filters)
@@ -140,7 +132,7 @@ class TestShowIPAvailability(TestIPAvailability):
_ip_availability.network_id,
_ip_availability.network_name,
_ip_availability.tenant_id,
- osc_utils.format_list(
+ common_utils.format_list(
_ip_availability.subnet_ip_availability),
_ip_availability.total_ips,
_ip_availability.used_ips,
diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py
index ba810f1..bb60681 100644
--- a/openstackclient/tests/network/v2/test_network.py
+++ b/openstackclient/tests/network/v2/test_network.py
@@ -11,12 +11,12 @@
# under the License.
#
-import copy
import mock
-
from mock import call
-from openstackclient.common import exceptions
-from openstackclient.common import utils
+
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.network.v2 import network
from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests import fakes
@@ -35,14 +35,20 @@ class TestNetwork(network_fakes.TestNetworkV2):
# Get a shortcut to the network client
self.network = self.app.client_manager.network
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+ # Get a shortcut to the DomainManager Mock
+ self.domains_mock = self.app.client_manager.identity.domains
class TestCreateNetworkIdentityV3(TestNetwork):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ domain = identity_fakes_v3.FakeDomain.create_one_domain()
# The new network created.
_network = network_fakes.FakeNetwork.create_one_network(
attrs={
- 'tenant_id': identity_fakes_v3.project_id,
+ 'tenant_id': project.id,
'availability_zone_hints': ["nova"],
}
)
@@ -54,6 +60,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
'id',
'is_default',
'name',
+ 'port_security_enabled',
'project_id',
'provider_network_type',
'router:external',
@@ -69,6 +76,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
_network.id,
_network.is_default,
_network.name,
+ _network.is_port_security_enabled,
_network.project_id,
_network.provider_network_type,
network._format_router_external(_network.is_router_external),
@@ -85,29 +93,8 @@ class TestCreateNetworkIdentityV3(TestNetwork):
# Get the command object to test
self.cmd = network.CreateNetwork(self.app, self.namespace)
- # Set identity client v3. And get a shortcut to Identity client.
- identity_client = identity_fakes_v3.FakeIdentityv3Client(
- endpoint=fakes.AUTH_URL,
- token=fakes.AUTH_TOKEN,
- )
- self.app.client_manager.identity = identity_client
- self.identity = self.app.client_manager.identity
-
- # Get a shortcut to the ProjectManager Mock
- self.projects_mock = self.identity.projects
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes_v3.PROJECT),
- loaded=True,
- )
-
- # Get a shortcut to the DomainManager Mock
- self.domains_mock = self.identity.domains
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes_v3.DOMAIN),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
+ self.domains_mock.get.return_value = self.domain
def test_create_no_options(self):
arglist = []
@@ -142,21 +129,22 @@ class TestCreateNetworkIdentityV3(TestNetwork):
arglist = [
"--disable",
"--share",
- "--project", identity_fakes_v3.project_name,
- "--project-domain", identity_fakes_v3.domain_name,
+ "--project", self.project.name,
+ "--project-domain", self.domain.name,
"--availability-zone-hint", "nova",
"--external", "--default",
"--provider-network-type", "vlan",
"--provider-physical-network", "physnet1",
"--provider-segment", "400",
"--transparent-vlan",
+ "--enable-port-security",
self._network.name,
]
verifylist = [
('disable', True),
('share', True),
- ('project', identity_fakes_v3.project_name),
- ('project_domain', identity_fakes_v3.domain_name),
+ ('project', self.project.name),
+ ('project_domain', self.domain.name),
('availability_zone_hints', ["nova"]),
('external', True),
('default', True),
@@ -164,6 +152,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
('physical_network', 'physnet1'),
('segmentation_id', '400'),
('transparent_vlan', True),
+ ('enable_port_security', True),
('name', self._network.name),
]
@@ -175,13 +164,14 @@ class TestCreateNetworkIdentityV3(TestNetwork):
'availability_zone_hints': ["nova"],
'name': self._network.name,
'shared': True,
- 'tenant_id': identity_fakes_v3.project_id,
+ 'tenant_id': self.project.id,
'is_default': True,
'router:external': True,
'provider:network_type': 'vlan',
'provider:physical_network': 'physnet1',
'provider:segmentation_id': '400',
'vlan_transparent': True,
+ 'port_security_enabled': True,
})
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -190,6 +180,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
arglist = [
"--enable",
"--no-share",
+ "--disable-port-security",
self._network.name,
]
verifylist = [
@@ -197,6 +188,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
('no_share', True),
('name', self._network.name),
('external', False),
+ ('disable_port_security', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -206,6 +198,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
'admin_state_up': True,
'name': self._network.name,
'shared': False,
+ 'port_security_enabled': False,
})
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -213,9 +206,10 @@ class TestCreateNetworkIdentityV3(TestNetwork):
class TestCreateNetworkIdentityV2(TestNetwork):
+ project = identity_fakes_v2.FakeProject.create_one_project()
# The new network created.
_network = network_fakes.FakeNetwork.create_one_network(
- attrs={'tenant_id': identity_fakes_v2.project_id}
+ attrs={'tenant_id': project.id}
)
columns = (
@@ -225,6 +219,7 @@ class TestCreateNetworkIdentityV2(TestNetwork):
'id',
'is_default',
'name',
+ 'port_security_enabled',
'project_id',
'provider_network_type',
'router:external',
@@ -240,6 +235,7 @@ class TestCreateNetworkIdentityV2(TestNetwork):
_network.id,
_network.is_default,
_network.name,
+ _network.is_port_security_enabled,
_network.project_id,
_network.provider_network_type,
network._format_router_external(_network.is_router_external),
@@ -266,24 +262,20 @@ class TestCreateNetworkIdentityV2(TestNetwork):
# Get a shortcut to the ProjectManager Mock
self.projects_mock = self.identity.tenants
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes_v2.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
# There is no DomainManager Mock in fake identity v2.
def test_create_with_project_identityv2(self):
arglist = [
- "--project", identity_fakes_v2.project_name,
+ "--project", self.project.name,
self._network.name,
]
verifylist = [
('enable', True),
('share', None),
('name', self._network.name),
- ('project', identity_fakes_v2.project_name),
+ ('project', self.project.name),
('external', False),
]
@@ -293,22 +285,22 @@ class TestCreateNetworkIdentityV2(TestNetwork):
self.network.create_network.assert_called_once_with(**{
'admin_state_up': True,
'name': self._network.name,
- 'tenant_id': identity_fakes_v2.project_id,
+ 'tenant_id': self.project.id,
})
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
def test_create_with_domain_identityv2(self):
arglist = [
- "--project", identity_fakes_v3.project_name,
- "--project-domain", identity_fakes_v3.domain_name,
+ "--project", self.project.name,
+ "--project-domain", "domain-name",
self._network.name,
]
verifylist = [
('enable', True),
('share', None),
- ('project', identity_fakes_v3.project_name),
- ('project_domain', identity_fakes_v3.domain_name),
+ ('project', self.project.name),
+ ('project_domain', "domain-name"),
('name', self._network.name),
('external', False),
]
@@ -546,6 +538,7 @@ class TestSetNetwork(TestNetwork):
'--provider-physical-network', 'physnet1',
'--provider-segment', '400',
'--no-transparent-vlan',
+ '--enable-port-security',
]
verifylist = [
('network', self._network.name),
@@ -558,6 +551,7 @@ class TestSetNetwork(TestNetwork):
('physical_network', 'physnet1'),
('segmentation_id', '400'),
('no_transparent_vlan', True),
+ ('enable_port_security', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -573,6 +567,7 @@ class TestSetNetwork(TestNetwork):
'provider:physical_network': 'physnet1',
'provider:segmentation_id': '400',
'vlan_transparent': False,
+ 'port_security_enabled': True,
}
self.network.update_network.assert_called_once_with(
self._network, **attrs)
@@ -584,12 +579,14 @@ class TestSetNetwork(TestNetwork):
'--disable',
'--no-share',
'--internal',
+ '--disable-port-security',
]
verifylist = [
('network', self._network.name),
('disable', True),
('no_share', True),
('internal', True),
+ ('disable_port_security', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -599,6 +596,7 @@ class TestSetNetwork(TestNetwork):
'admin_state_up': False,
'shared': False,
'router:external': False,
+ 'port_security_enabled': False,
}
self.network.update_network.assert_called_once_with(
self._network, **attrs)
@@ -609,8 +607,12 @@ class TestSetNetwork(TestNetwork):
verifylist = [('network', self._network.name), ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.assertRaises(exceptions.CommandError, self.cmd.take_action,
- parsed_args)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {}
+ self.network.update_network.assert_called_once_with(
+ self._network, **attrs)
+ self.assertIsNone(result)
class TestShowNetwork(TestNetwork):
@@ -625,6 +627,7 @@ class TestShowNetwork(TestNetwork):
'id',
'is_default',
'name',
+ 'port_security_enabled',
'project_id',
'provider_network_type',
'router:external',
@@ -640,6 +643,7 @@ class TestShowNetwork(TestNetwork):
_network.id,
_network.is_default,
_network.name,
+ _network.is_port_security_enabled,
_network.project_id,
_network.provider_network_type,
network._format_router_external(_network.is_router_external),
diff --git a/openstackclient/tests/network/v2/test_network_agent.py b/openstackclient/tests/network/v2/test_network_agent.py
new file mode 100644
index 0000000..269d4e1
--- /dev/null
+++ b/openstackclient/tests/network/v2/test_network_agent.py
@@ -0,0 +1,294 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import mock
+from mock import call
+
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.network.v2 import network_agent
+from openstackclient.tests.network.v2 import fakes as network_fakes
+from openstackclient.tests import utils as tests_utils
+
+
+class TestNetworkAgent(network_fakes.TestNetworkV2):
+
+ def setUp(self):
+ super(TestNetworkAgent, self).setUp()
+
+ # Get a shortcut to the network client
+ self.network = self.app.client_manager.network
+
+
+class TestDeleteNetworkAgent(TestNetworkAgent):
+
+ network_agents = (
+ network_fakes.FakeNetworkAgent.create_network_agents(count=2))
+
+ def setUp(self):
+ super(TestDeleteNetworkAgent, self).setUp()
+ self.network.delete_agent = mock.Mock(return_value=None)
+ self.network.get_agent = (
+ network_fakes.FakeNetworkAgent.get_network_agents(
+ agents=self.network_agents)
+ )
+
+ # Get the command object to test
+ self.cmd = network_agent.DeleteNetworkAgent(self.app, self.namespace)
+
+ def test_network_agent_delete(self):
+ arglist = [
+ self.network_agents[0].id,
+ ]
+ verifylist = [
+ ('network_agent', [self.network_agents[0].id]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.get_agent.assert_called_once_with(
+ self.network_agents[0].id, ignore_missing=False)
+ self.network.delete_agent.assert_called_once_with(
+ self.network_agents[0])
+ self.assertIsNone(result)
+
+ def test_multi_network_agents_delete(self):
+ arglist = []
+ verifylist = []
+
+ for n in self.network_agents:
+ arglist.append(n.id)
+ verifylist = [
+ ('network_agent', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for n in self.network_agents:
+ calls.append(call(n))
+ self.network.delete_agent.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_multi_network_agents_delete_with_exception(self):
+ arglist = [
+ self.network_agents[0].id,
+ 'unexist_network_agent',
+ ]
+ verifylist = [
+ ('network_agent',
+ [self.network_agents[0].id, 'unexist_network_agent']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self.network_agents[0], exceptions.CommandError]
+ self.network.get_agent = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 network agents failed to delete.', str(e))
+
+ self.network.get_agent.assert_any_call(
+ self.network_agents[0].id, ignore_missing=False)
+ self.network.get_agent.assert_any_call(
+ 'unexist_network_agent', ignore_missing=False)
+ self.network.delete_agent.assert_called_once_with(
+ self.network_agents[0]
+ )
+
+
+class TestListNetworkAgent(TestNetworkAgent):
+
+ network_agents = (
+ network_fakes.FakeNetworkAgent.create_network_agents(count=3))
+
+ columns = (
+ 'ID',
+ 'Agent Type',
+ 'Host',
+ 'Availability Zone',
+ 'Alive',
+ 'State',
+ 'Binary'
+ )
+ data = []
+ for agent in network_agents:
+ data.append((
+ agent.id,
+ agent.agent_type,
+ agent.host,
+ agent.availability_zone,
+ agent.alive,
+ network_agent._format_admin_state(agent.admin_state_up),
+ agent.binary,
+ ))
+
+ def setUp(self):
+ super(TestListNetworkAgent, self).setUp()
+ self.network.agents = mock.Mock(
+ return_value=self.network_agents)
+
+ # Get the command object to test
+ self.cmd = network_agent.ListNetworkAgent(self.app, self.namespace)
+
+ def test_network_agents_list(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.agents.assert_called_once_with(**{})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
+class TestSetNetworkAgent(TestNetworkAgent):
+
+ _network_agent = (
+ network_fakes.FakeNetworkAgent.create_one_network_agent())
+
+ def setUp(self):
+ super(TestSetNetworkAgent, self).setUp()
+ self.network.update_agent = mock.Mock(return_value=None)
+ self.network.get_agent = mock.Mock(return_value=self._network_agent)
+
+ # Get the command object to test
+ self.cmd = network_agent.SetNetworkAgent(self.app, self.namespace)
+
+ def test_set_nothing(self):
+ arglist = [
+ self._network_agent.id,
+ ]
+ verifylist = [
+ ('network_agent', self._network_agent.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {}
+ self.network.update_agent.assert_called_once_with(
+ self._network_agent, **attrs)
+ self.assertIsNone(result)
+
+ def test_set_all(self):
+ arglist = [
+ '--description', 'new_description',
+ '--enable',
+ self._network_agent.id,
+ ]
+ verifylist = [
+ ('description', 'new_description'),
+ ('enable', True),
+ ('disable', False),
+ ('network_agent', self._network_agent.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'description': 'new_description',
+ 'admin_state_up': True,
+ }
+ self.network.update_agent.assert_called_once_with(
+ self._network_agent, **attrs)
+ self.assertIsNone(result)
+
+ def test_set_with_disable(self):
+ arglist = [
+ '--disable',
+ self._network_agent.id,
+ ]
+ verifylist = [
+ ('enable', False),
+ ('disable', True),
+ ('network_agent', self._network_agent.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'admin_state_up': False,
+ }
+ self.network.update_agent.assert_called_once_with(
+ self._network_agent, **attrs)
+ self.assertIsNone(result)
+
+
+class TestShowNetworkAgent(TestNetworkAgent):
+
+ _network_agent = (
+ network_fakes.FakeNetworkAgent.create_one_network_agent())
+
+ columns = (
+ 'admin_state_up',
+ 'agent_type',
+ 'alive',
+ 'availability_zone',
+ 'binary',
+ 'configurations',
+ 'host',
+ 'id',
+ )
+ data = (
+ network_agent._format_admin_state(_network_agent.admin_state_up),
+ _network_agent.agent_type,
+ _network_agent.alive,
+ _network_agent.availability_zone,
+ _network_agent.binary,
+ utils.format_dict(_network_agent.configurations),
+ _network_agent.host,
+ _network_agent.id,
+ )
+
+ def setUp(self):
+ super(TestShowNetworkAgent, self).setUp()
+ self.network.get_agent = mock.Mock(
+ return_value=self._network_agent)
+
+ # Get the command object to test
+ self.cmd = network_agent.ShowNetworkAgent(self.app, self.namespace)
+
+ def test_show_no_options(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_show_all_options(self):
+ arglist = [
+ self._network_agent.id,
+ ]
+ verifylist = [
+ ('network_agent', self._network_agent.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.get_agent.assert_called_once_with(
+ self._network_agent.id, ignore_missing=False)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(list(self.data), list(data))
diff --git a/openstackclient/tests/network/v2/test_network_rbac.py b/openstackclient/tests/network/v2/test_network_rbac.py
new file mode 100644
index 0000000..9250e91
--- /dev/null
+++ b/openstackclient/tests/network/v2/test_network_rbac.py
@@ -0,0 +1,430 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import mock
+from mock import call
+
+from osc_lib import exceptions
+
+from openstackclient.network.v2 import network_rbac
+from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3
+from openstackclient.tests.network.v2 import fakes as network_fakes
+from openstackclient.tests import utils as tests_utils
+
+
+class TestNetworkRBAC(network_fakes.TestNetworkV2):
+
+ def setUp(self):
+ super(TestNetworkRBAC, self).setUp()
+
+ # Get a shortcut to the network client
+ self.network = self.app.client_manager.network
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+
+
+class TestCreateNetworkRBAC(TestNetworkRBAC):
+
+ network_object = network_fakes.FakeNetwork.create_one_network()
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac(
+ attrs={'tenant_id': project.id,
+ 'target_tenant': project.id,
+ 'object_id': network_object.id}
+ )
+
+ columns = (
+ 'action',
+ 'id',
+ 'object_id',
+ 'object_type',
+ 'project_id',
+ 'target_project_id',
+ )
+
+ data = [
+ rbac_policy.action,
+ rbac_policy.id,
+ rbac_policy.object_id,
+ rbac_policy.object_type,
+ rbac_policy.tenant_id,
+ rbac_policy.target_tenant,
+ ]
+
+ def setUp(self):
+ super(TestCreateNetworkRBAC, self).setUp()
+
+ # Get the command object to test
+ self.cmd = network_rbac.CreateNetworkRBAC(self.app, self.namespace)
+
+ self.network.create_rbac_policy = mock.Mock(
+ return_value=self.rbac_policy)
+ self.network.find_network = mock.Mock(
+ return_value=self.network_object)
+ self.projects_mock.get.return_value = self.project
+
+ def test_network_rbac_create_no_type(self):
+ arglist = [
+ '--action', self.rbac_policy.action,
+ self.rbac_policy.object_id,
+ ]
+ verifylist = [
+ ('action', self.rbac_policy.action),
+ ('rbac_policy', self.rbac_policy.id),
+ ]
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_network_rbac_create_no_action(self):
+ arglist = [
+ '--type', self.rbac_policy.object_type,
+ self.rbac_policy.object_id,
+ ]
+ verifylist = [
+ ('type', self.rbac_policy.object_type),
+ ('rbac_policy', self.rbac_policy.id),
+ ]
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_network_rbac_create_invalid_type(self):
+ arglist = [
+ '--action', self.rbac_policy.action,
+ '--type', 'invalid_type',
+ '--target-project', self.rbac_policy.target_tenant,
+ self.rbac_policy.object_id,
+ ]
+ verifylist = [
+ ('action', self.rbac_policy.action),
+ ('type', 'invalid_type'),
+ ('target-project', self.rbac_policy.target_tenant),
+ ('rbac_policy', self.rbac_policy.id),
+ ]
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_network_rbac_create_invalid_action(self):
+ arglist = [
+ '--type', self.rbac_policy.object_type,
+ '--action', 'invalid_action',
+ '--target-project', self.rbac_policy.target_tenant,
+ self.rbac_policy.object_id,
+ ]
+ verifylist = [
+ ('type', self.rbac_policy.object_type),
+ ('action', 'invalid_action'),
+ ('target-project', self.rbac_policy.target_tenant),
+ ('rbac_policy', self.rbac_policy.id),
+ ]
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_network_rbac_create(self):
+ arglist = [
+ '--type', self.rbac_policy.object_type,
+ '--action', self.rbac_policy.action,
+ '--target-project', self.rbac_policy.target_tenant,
+ self.rbac_policy.object_id,
+ ]
+ verifylist = [
+ ('type', self.rbac_policy.object_type),
+ ('action', self.rbac_policy.action),
+ ('target_project', self.rbac_policy.target_tenant),
+ ('rbac_object', self.rbac_policy.object_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_rbac_policy.assert_called_with(**{
+ 'object_id': self.rbac_policy.object_id,
+ 'object_type': self.rbac_policy.object_type,
+ 'action': self.rbac_policy.action,
+ 'target_tenant': self.rbac_policy.target_tenant,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_network_rbac_create_all_options(self):
+ arglist = [
+ '--type', self.rbac_policy.object_type,
+ '--action', self.rbac_policy.action,
+ '--target-project', self.rbac_policy.target_tenant,
+ '--project', self.rbac_policy.tenant_id,
+ '--project-domain', self.project.domain_id,
+ '--target-project-domain', self.project.domain_id,
+ self.rbac_policy.object_id,
+ ]
+ verifylist = [
+ ('type', self.rbac_policy.object_type),
+ ('action', self.rbac_policy.action),
+ ('target_project', self.rbac_policy.target_tenant),
+ ('project', self.rbac_policy.tenant_id),
+ ('project_domain', self.project.domain_id),
+ ('target_project_domain', self.project.domain_id),
+ ('rbac_object', self.rbac_policy.object_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_rbac_policy.assert_called_with(**{
+ 'object_id': self.rbac_policy.object_id,
+ 'object_type': self.rbac_policy.object_type,
+ 'action': self.rbac_policy.action,
+ 'target_tenant': self.rbac_policy.target_tenant,
+ 'tenant_id': self.rbac_policy.tenant_id,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
+class TestDeleteNetworkRBAC(TestNetworkRBAC):
+
+ rbac_policies = network_fakes.FakeNetworkRBAC.create_network_rbacs(count=2)
+
+ def setUp(self):
+ super(TestDeleteNetworkRBAC, self).setUp()
+ self.network.delete_rbac_policy = mock.Mock(return_value=None)
+ self.network.find_rbac_policy = (
+ network_fakes.FakeNetworkRBAC.get_network_rbacs(
+ rbac_policies=self.rbac_policies)
+ )
+
+ # Get the command object to test
+ self.cmd = network_rbac.DeleteNetworkRBAC(self.app, self.namespace)
+
+ def test_network_rbac_delete(self):
+ arglist = [
+ self.rbac_policies[0].id,
+ ]
+ verifylist = [
+ ('rbac_policy', [self.rbac_policies[0].id]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.find_rbac_policy.assert_called_once_with(
+ self.rbac_policies[0].id, ignore_missing=False)
+ self.network.delete_rbac_policy.assert_called_once_with(
+ self.rbac_policies[0])
+ self.assertIsNone(result)
+
+ def test_multi_network_rbacs_delete(self):
+ arglist = []
+ verifylist = []
+
+ for r in self.rbac_policies:
+ arglist.append(r.id)
+ verifylist = [
+ ('rbac_policy', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for r in self.rbac_policies:
+ calls.append(call(r))
+ self.network.delete_rbac_policy.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_multi_network_policies_delete_with_exception(self):
+ arglist = [
+ self.rbac_policies[0].id,
+ 'unexist_rbac_policy',
+ ]
+ verifylist = [
+ ('rbac_policy',
+ [self.rbac_policies[0].id, 'unexist_rbac_policy']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self.rbac_policies[0], exceptions.CommandError]
+ self.network.find_rbac_policy = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 RBAC policies failed to delete.', str(e))
+
+ self.network.find_rbac_policy.assert_any_call(
+ self.rbac_policies[0].id, ignore_missing=False)
+ self.network.find_rbac_policy.assert_any_call(
+ 'unexist_rbac_policy', ignore_missing=False)
+ self.network.delete_rbac_policy.assert_called_once_with(
+ self.rbac_policies[0]
+ )
+
+
+class TestListNetworkRABC(TestNetworkRBAC):
+
+ # The network rbac policies going to be listed up.
+ rbac_policies = network_fakes.FakeNetworkRBAC.create_network_rbacs(count=3)
+
+ columns = (
+ 'ID',
+ 'Object Type',
+ 'Object ID',
+ )
+
+ data = []
+ for r in rbac_policies:
+ data.append((
+ r.id,
+ r.object_type,
+ r.object_id,
+ ))
+
+ def setUp(self):
+ super(TestListNetworkRABC, self).setUp()
+
+ # Get the command object to test
+ self.cmd = network_rbac.ListNetworkRBAC(self.app, self.namespace)
+
+ self.network.rbac_policies = mock.Mock(return_value=self.rbac_policies)
+
+ def test_network_rbac_list(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.rbac_policies.assert_called_with()
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
+class TestSetNetworkRBAC(TestNetworkRBAC):
+
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac(
+ attrs={'target_tenant': project.id})
+
+ def setUp(self):
+ super(TestSetNetworkRBAC, self).setUp()
+
+ # Get the command object to test
+ self.cmd = network_rbac.SetNetworkRBAC(self.app, self.namespace)
+
+ self.network.find_rbac_policy = mock.Mock(
+ return_value=self.rbac_policy)
+ self.network.update_rbac_policy = mock.Mock(return_value=None)
+ self.projects_mock.get.return_value = self.project
+
+ def test_network_rbac_set_nothing(self):
+ arglist = [
+ self.rbac_policy.id,
+ ]
+ verifylist = [
+ ('rbac_policy', self.rbac_policy.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.find_rbac_policy.assert_called_once_with(
+ self.rbac_policy.id, ignore_missing=False
+ )
+ attrs = {}
+ self.network.update_rbac_policy.assert_called_once_with(
+ self.rbac_policy, **attrs)
+ self.assertIsNone(result)
+
+ def test_network_rbac_set(self):
+ arglist = [
+ '--target-project', self.project.id,
+ self.rbac_policy.id,
+ ]
+ verifylist = [
+ ('target_project', self.project.id),
+ ('rbac_policy', self.rbac_policy.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.find_rbac_policy.assert_called_once_with(
+ self.rbac_policy.id, ignore_missing=False
+ )
+ attrs = {'target_tenant': self.project.id}
+ self.network.update_rbac_policy.assert_called_once_with(
+ self.rbac_policy, **attrs)
+ self.assertIsNone(result)
+
+
+class TestShowNetworkRBAC(TestNetworkRBAC):
+
+ rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac()
+
+ columns = (
+ 'action',
+ 'id',
+ 'object_id',
+ 'object_type',
+ 'project_id',
+ 'target_project_id',
+ )
+
+ data = [
+ rbac_policy.action,
+ rbac_policy.id,
+ rbac_policy.object_id,
+ rbac_policy.object_type,
+ rbac_policy.tenant_id,
+ rbac_policy.target_tenant,
+ ]
+
+ def setUp(self):
+ super(TestShowNetworkRBAC, self).setUp()
+
+ # Get the command object to test
+ self.cmd = network_rbac.ShowNetworkRBAC(self.app, self.namespace)
+
+ self.network.find_rbac_policy = mock.Mock(
+ return_value=self.rbac_policy)
+
+ def test_show_no_options(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_network_rbac_show_all_options(self):
+ arglist = [
+ self.rbac_policy.id,
+ ]
+ verifylist = [
+ ('rbac_policy', self.rbac_policy.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.find_rbac_policy.assert_called_with(
+ self.rbac_policy.id, ignore_missing=False
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
diff --git a/openstackclient/tests/network/v2/test_network_segment.py b/openstackclient/tests/network/v2/test_network_segment.py
index 0a99ece..a635d84 100644
--- a/openstackclient/tests/network/v2/test_network_segment.py
+++ b/openstackclient/tests/network/v2/test_network_segment.py
@@ -13,7 +13,8 @@
import mock
-from openstackclient.common import exceptions
+from osc_lib import exceptions
+
from openstackclient.network.v2 import network_segment
from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils as tests_utils
diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py
index dd98a63..a1cecec 100644
--- a/openstackclient/tests/network/v2/test_port.py
+++ b/openstackclient/tests/network/v2/test_port.py
@@ -11,11 +11,13 @@
# under the License.
#
+import argparse
import mock
from mock import call
-from openstackclient.common import exceptions
-from openstackclient.common import utils
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.network.v2 import port
from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils as tests_utils
@@ -173,6 +175,58 @@ class TestCreatePort(TestPort):
self.assertEqual(ref_columns, columns)
self.assertEqual(ref_data, data)
+ def test_create_invalid_json_binding_profile(self):
+ arglist = [
+ '--network', self._port.network_id,
+ '--binding-profile', '{"parent_name":"fake_parent"',
+ 'test-port',
+ ]
+ self.assertRaises(argparse.ArgumentTypeError,
+ self.check_parser,
+ self.cmd,
+ arglist,
+ None)
+
+ def test_create_invalid_key_value_binding_profile(self):
+ arglist = [
+ '--network', self._port.network_id,
+ '--binding-profile', 'key',
+ 'test-port',
+ ]
+ self.assertRaises(argparse.ArgumentTypeError,
+ self.check_parser,
+ self.cmd,
+ arglist,
+ None)
+
+ def test_create_json_binding_profile(self):
+ arglist = [
+ '--network', self._port.network_id,
+ '--binding-profile', '{"parent_name":"fake_parent"}',
+ '--binding-profile', '{"tag":42}',
+ 'test-port',
+ ]
+ verifylist = [
+ ('network', self._port.network_id,),
+ ('enable', True),
+ ('binding_profile', {'parent_name': 'fake_parent', 'tag': 42}),
+ ('name', 'test-port'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_port.assert_called_once_with(**{
+ 'admin_state_up': True,
+ 'network_id': self._port.network_id,
+ 'binding:profile': {'parent_name': 'fake_parent', 'tag': 42},
+ 'name': 'test-port',
+ })
+
+ ref_columns, ref_data = self._get_common_cols_data(self._port)
+ self.assertEqual(ref_columns, columns)
+ self.assertEqual(ref_data, data)
+
class TestDeletePort(TestPort):
@@ -315,6 +369,47 @@ class TestListPort(TestPort):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_port_list_device_owner_opt(self):
+ arglist = [
+ '--device-owner', self._ports[0].device_owner,
+ ]
+
+ verifylist = [
+ ('device_owner', self._ports[0].device_owner)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ports.assert_called_once_with(**{
+ 'device_owner': self._ports[0].device_owner
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_port_list_all_opt(self):
+ arglist = [
+ '--device-owner', self._ports[0].device_owner,
+ '--router', 'fake-router-name',
+ ]
+
+ verifylist = [
+ ('device_owner', self._ports[0].device_owner),
+ ('router', 'fake-router-name')
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ports.assert_called_once_with(**{
+ 'device_owner': self._ports[0].device_owner,
+ 'device_id': 'fake-router-id'
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
class TestSetPort(TestPort):
@@ -426,6 +521,63 @@ class TestSetPort(TestPort):
self.network.update_port.assert_called_once_with(self._port, **attrs)
self.assertIsNone(result)
+ def test_set_nothing(self):
+ arglist = [
+ self._port.name,
+ ]
+ verifylist = [
+ ('port', self._port.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {}
+ self.network.update_port.assert_called_once_with(self._port, **attrs)
+ self.assertIsNone(result)
+
+ def test_set_invalid_json_binding_profile(self):
+ arglist = [
+ '--binding-profile', '{"parent_name"}',
+ 'test-port',
+ ]
+ self.assertRaises(argparse.ArgumentTypeError,
+ self.check_parser,
+ self.cmd,
+ arglist,
+ None)
+
+ def test_set_invalid_key_value_binding_profile(self):
+ arglist = [
+ '--binding-profile', 'key',
+ 'test-port',
+ ]
+ self.assertRaises(argparse.ArgumentTypeError,
+ self.check_parser,
+ self.cmd,
+ arglist,
+ None)
+
+ def test_set_mixed_binding_profile(self):
+ arglist = [
+ '--binding-profile', 'foo=bar',
+ '--binding-profile', '{"foo2": "bar2"}',
+ self._port.name,
+ ]
+ verifylist = [
+ ('binding_profile', {'foo': 'bar', 'foo2': 'bar2'}),
+ ('port', self._port.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'binding:profile': {'foo': 'bar', 'foo2': 'bar2'},
+ }
+ self.network.update_port.assert_called_once_with(self._port, **attrs)
+ self.assertIsNone(result)
+
class TestShowPort(TestPort):
@@ -464,3 +616,81 @@ class TestShowPort(TestPort):
ref_columns, ref_data = self._get_common_cols_data(self._port)
self.assertEqual(ref_columns, columns)
self.assertEqual(ref_data, data)
+
+
+class TestUnsetPort(TestPort):
+
+ def setUp(self):
+ super(TestUnsetPort, self).setUp()
+ self._testport = network_fakes.FakePort.create_one_port(
+ {'fixed_ips': [{'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152',
+ 'ip_address': '0.0.0.1'},
+ {'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152',
+ 'ip_address': '1.0.0.0'}],
+ 'binding:profile': {'batman': 'Joker', 'Superman': 'LexLuthor'}})
+ self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet(
+ {'id': '042eb10a-3a18-4658-ab-cf47c8d03152'})
+ self.network.find_subnet = mock.Mock(return_value=self.fake_subnet)
+ self.network.find_port = mock.Mock(return_value=self._testport)
+ self.network.update_port = mock.Mock(return_value=None)
+ # Get the command object to test
+ self.cmd = port.UnsetPort(self.app, self.namespace)
+
+ def test_unset_port_parameters(self):
+ arglist = [
+ '--fixed-ip',
+ 'subnet=042eb10a-3a18-4658-ab-cf47c8d03152,ip-address=1.0.0.0',
+ '--binding-profile', 'Superman',
+ self._testport.name,
+ ]
+ verifylist = [
+ ('fixed_ip', [{
+ 'subnet': '042eb10a-3a18-4658-ab-cf47c8d03152',
+ 'ip-address': '1.0.0.0'}]),
+ ('binding_profile', ['Superman']),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'fixed_ips': [{
+ 'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152',
+ 'ip_address': '0.0.0.1'}],
+ 'binding:profile': {'batman': 'Joker'}
+ }
+ self.network.update_port.assert_called_once_with(
+ self._testport, **attrs)
+ self.assertIsNone(result)
+
+ def test_unset_port_fixed_ip_not_existent(self):
+ arglist = [
+ '--fixed-ip', 'ip-address=1.0.0.1',
+ '--binding-profile', 'Superman',
+ self._testport.name,
+ ]
+ verifylist = [
+ ('fixed_ip', [{'ip-address': '1.0.0.1'}]),
+ ('binding_profile', ['Superman']),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+
+ def test_unset_port_binding_profile_not_existent(self):
+ arglist = [
+ '--fixed-ip', 'ip-address=1.0.0.0',
+ '--binding-profile', 'Neo',
+ self._testport.name,
+ ]
+ verifylist = [
+ ('fixed_ip', [{'ip-address': '1.0.0.0'}]),
+ ('binding_profile', ['Neo']),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py
index 99b41d2..1ef4707 100644
--- a/openstackclient/tests/network/v2/test_router.py
+++ b/openstackclient/tests/network/v2/test_router.py
@@ -12,9 +12,11 @@
#
import mock
+from mock import call
+
+from osc_lib import exceptions
+from osc_lib import utils as osc_utils
-from openstackclient.common import exceptions
-from openstackclient.common import utils as osc_utils
from openstackclient.network.v2 import router
from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils as tests_utils
@@ -202,32 +204,82 @@ class TestCreateRouter(TestRouter):
class TestDeleteRouter(TestRouter):
- # The router to delete.
- _router = network_fakes.FakeRouter.create_one_router()
+ # The routers to delete.
+ _routers = network_fakes.FakeRouter.create_routers(count=2)
def setUp(self):
super(TestDeleteRouter, self).setUp()
self.network.delete_router = mock.Mock(return_value=None)
- self.network.find_router = mock.Mock(return_value=self._router)
+ self.network.find_router = (
+ network_fakes.FakeRouter.get_routers(self._routers))
# Get the command object to test
self.cmd = router.DeleteRouter(self.app, self.namespace)
- def test_delete(self):
+ def test_router_delete(self):
arglist = [
- self._router.name,
+ self._routers[0].name,
]
verifylist = [
- ('router', [self._router.name]),
+ ('router', [self._routers[0].name]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.network.delete_router.assert_called_once_with(self._router)
+ self.network.delete_router.assert_called_once_with(self._routers[0])
self.assertIsNone(result)
+ def test_multi_routers_delete(self):
+ arglist = []
+ verifylist = []
+
+ for r in self._routers:
+ arglist.append(r.name)
+ verifylist = [
+ ('router', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for r in self._routers:
+ calls.append(call(r))
+ self.network.delete_router.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_multi_routers_delete_with_exception(self):
+ arglist = [
+ self._routers[0].name,
+ 'unexist_router',
+ ]
+ verifylist = [
+ ('router',
+ [self._routers[0].name, 'unexist_router']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self._routers[0], exceptions.CommandError]
+ self.network.find_router = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 routers failed to delete.', str(e))
+
+ self.network.find_router.assert_any_call(
+ self._routers[0].name, ignore_missing=False)
+ self.network.find_router.assert_any_call(
+ 'unexist_router', ignore_missing=False)
+ self.network.delete_router.assert_called_once_with(
+ self._routers[0]
+ )
+
class TestListRouter(TestRouter):
@@ -568,12 +620,20 @@ class TestSetRouter(TestRouter):
self.cmd, arglist, verifylist)
def test_set_nothing(self):
- arglist = [self._router.name, ]
- verifylist = [('router', self._router.name), ]
+ arglist = [
+ self._router.name,
+ ]
+ verifylist = [
+ ('router', self._router.name),
+ ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.assertRaises(exceptions.CommandError, self.cmd.take_action,
- parsed_args)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {}
+ self.network.update_router.assert_called_once_with(
+ self._router, **attrs)
+ self.assertIsNone(result)
class TestShowRouter(TestRouter):
@@ -638,3 +698,54 @@ class TestShowRouter(TestRouter):
self._router.name, ignore_missing=False)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+
+
+class TestUnsetRouter(TestRouter):
+
+ def setUp(self):
+ super(TestUnsetRouter, self).setUp()
+ self._testrouter = network_fakes.FakeRouter.create_one_router(
+ {'routes': [{"destination": "192.168.101.1/24",
+ "gateway": "172.24.4.3"},
+ {"destination": "192.168.101.2/24",
+ "gateway": "172.24.4.3"}], })
+ self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet()
+ self.network.find_router = mock.Mock(return_value=self._testrouter)
+ self.network.update_router = mock.Mock(return_value=None)
+ # Get the command object to test
+ self.cmd = router.UnsetRouter(self.app, self.namespace)
+
+ def test_unset_router_params(self):
+ arglist = [
+ '--route', 'destination=192.168.101.1/24,gateway=172.24.4.3',
+ self._testrouter.name,
+ ]
+ verifylist = [
+ ('routes', [
+ {"destination": "192.168.101.1/24", "gateway": "172.24.4.3"}]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'routes': [{"destination": "192.168.101.2/24",
+ "nexthop": "172.24.4.3"}],
+ }
+ self.network.update_router.assert_called_once_with(
+ self._testrouter, **attrs)
+ self.assertIsNone(result)
+
+ def test_unset_router_wrong_routes(self):
+ arglist = [
+ '--route', 'destination=192.168.101.1/24,gateway=172.24.4.2',
+ self._testrouter.name,
+ ]
+ verifylist = [
+ ('routes', [
+ {"destination": "192.168.101.1/24", "gateway": "172.24.4.2"}]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py
index 213367a..15f4cff 100644
--- a/openstackclient/tests/network/v2/test_security_group.py
+++ b/openstackclient/tests/network/v2/test_security_group.py
@@ -11,12 +11,13 @@
# under the License.
#
-import copy
import mock
+from mock import call
+
+from osc_lib import exceptions
from openstackclient.network.v2 import security_group
from openstackclient.tests.compute.v2 import fakes as compute_fakes
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils as tests_utils
@@ -29,6 +30,10 @@ class TestSecurityGroupNetwork(network_fakes.TestNetworkV2):
# Get a shortcut to the network client
self.network = self.app.client_manager.network
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+ # Get a shortcut to the DomainManager Mock
+ self.domains_mock = self.app.client_manager.identity.domains
class TestSecurityGroupCompute(compute_fakes.TestComputev2):
@@ -42,6 +47,8 @@ class TestSecurityGroupCompute(compute_fakes.TestComputev2):
class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork):
+ project = identity_fakes.FakeProject.create_one_project()
+ domain = identity_fakes.FakeDomain.create_one_domain()
# The security group to be created.
_security_group = \
network_fakes.FakeSecurityGroup.create_one_security_group()
@@ -68,29 +75,8 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork):
self.network.create_security_group = mock.Mock(
return_value=self._security_group)
- # Set identity client v3. And get a shortcut to Identity client.
- identity_client = identity_fakes.FakeIdentityv3Client(
- endpoint=fakes.AUTH_URL,
- token=fakes.AUTH_TOKEN,
- )
- self.app.client_manager.identity = identity_client
- self.identity = self.app.client_manager.identity
-
- # Get a shortcut to the ProjectManager Mock
- self.projects_mock = self.identity.projects
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
-
- # Get a shortcut to the DomainManager Mock
- self.domains_mock = self.identity.domains
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
+ self.domains_mock.get.return_value = self.domain
# Get the command object to test
self.cmd = security_group.CreateSecurityGroup(self.app, self.namespace)
@@ -120,15 +106,15 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork):
def test_create_all_options(self):
arglist = [
'--description', self._security_group.description,
- '--project', identity_fakes.project_name,
- '--project-domain', identity_fakes.domain_name,
+ '--project', self.project.name,
+ '--project-domain', self.domain.name,
self._security_group.name,
]
verifylist = [
('description', self._security_group.description),
('name', self._security_group.name),
- ('project', identity_fakes.project_name),
- ('project_domain', identity_fakes.domain_name),
+ ('project', self.project.name),
+ ('project_domain', self.domain.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -137,7 +123,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork):
self.network.create_security_group.assert_called_once_with(**{
'description': self._security_group.description,
'name': self._security_group.name,
- 'tenant_id': identity_fakes.project_id,
+ 'tenant_id': self.project.id,
})
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -145,6 +131,8 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork):
class TestCreateSecurityGroupCompute(TestSecurityGroupCompute):
+ project = identity_fakes.FakeProject.create_one_project()
+ domain = identity_fakes.FakeDomain.create_one_domain()
# The security group to be shown.
_security_group = \
compute_fakes.FakeSecurityGroup.create_one_security_group()
@@ -181,8 +169,8 @@ class TestCreateSecurityGroupCompute(TestSecurityGroupCompute):
def test_create_network_options(self):
arglist = [
- '--project', identity_fakes.project_name,
- '--project-domain', identity_fakes.domain_name,
+ '--project', self.project.name,
+ '--project-domain', self.domain.name,
self._security_group.name,
]
self.assertRaises(tests_utils.ParserException,
@@ -227,42 +215,93 @@ class TestCreateSecurityGroupCompute(TestSecurityGroupCompute):
class TestDeleteSecurityGroupNetwork(TestSecurityGroupNetwork):
- # The security group to be deleted.
- _security_group = \
- network_fakes.FakeSecurityGroup.create_one_security_group()
+ # The security groups to be deleted.
+ _security_groups = \
+ network_fakes.FakeSecurityGroup.create_security_groups()
def setUp(self):
super(TestDeleteSecurityGroupNetwork, self).setUp()
self.network.delete_security_group = mock.Mock(return_value=None)
- self.network.find_security_group = mock.Mock(
- return_value=self._security_group)
+ self.network.find_security_group = (
+ network_fakes.FakeSecurityGroup.get_security_groups(
+ self._security_groups)
+ )
# Get the command object to test
self.cmd = security_group.DeleteSecurityGroup(self.app, self.namespace)
def test_security_group_delete(self):
arglist = [
- self._security_group.name,
+ self._security_groups[0].name,
]
verifylist = [
- ('group', self._security_group.name),
+ ('group', [self._security_groups[0].name]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.network.delete_security_group.assert_called_once_with(
- self._security_group)
+ self._security_groups[0])
self.assertIsNone(result)
+ def test_multi_security_groups_delete(self):
+ arglist = []
+ verifylist = []
+
+ for s in self._security_groups:
+ arglist.append(s.name)
+ verifylist = [
+ ('group', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for s in self._security_groups:
+ calls.append(call(s))
+ self.network.delete_security_group.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_multi_security_groups_delete_with_exception(self):
+ arglist = [
+ self._security_groups[0].name,
+ 'unexist_security_group',
+ ]
+ verifylist = [
+ ('group',
+ [self._security_groups[0].name, 'unexist_security_group']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self._security_groups[0], exceptions.CommandError]
+ self.network.find_security_group = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 groups failed to delete.', str(e))
+
+ self.network.find_security_group.assert_any_call(
+ self._security_groups[0].name, ignore_missing=False)
+ self.network.find_security_group.assert_any_call(
+ 'unexist_security_group', ignore_missing=False)
+ self.network.delete_security_group.assert_called_once_with(
+ self._security_groups[0]
+ )
+
class TestDeleteSecurityGroupCompute(TestSecurityGroupCompute):
- # The security group to be deleted.
- _security_group = \
- compute_fakes.FakeSecurityGroup.create_one_security_group()
+ # The security groups to be deleted.
+ _security_groups = \
+ compute_fakes.FakeSecurityGroup.create_security_groups()
def setUp(self):
super(TestDeleteSecurityGroupCompute, self).setUp()
@@ -271,27 +310,80 @@ class TestDeleteSecurityGroupCompute(TestSecurityGroupCompute):
self.compute.security_groups.delete = mock.Mock(return_value=None)
- self.compute.security_groups.get = mock.Mock(
- return_value=self._security_group)
+ self.compute.security_groups.get = (
+ compute_fakes.FakeSecurityGroup.get_security_groups(
+ self._security_groups)
+ )
# Get the command object to test
self.cmd = security_group.DeleteSecurityGroup(self.app, None)
def test_security_group_delete(self):
arglist = [
- self._security_group.name,
+ self._security_groups[0].id,
]
verifylist = [
- ('group', self._security_group.name),
+ ('group', [self._security_groups[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.compute.security_groups.delete.assert_called_once_with(
- self._security_group.id)
+ self._security_groups[0].id)
+ self.assertIsNone(result)
+
+ def test_multi_security_groups_delete(self):
+ arglist = []
+ verifylist = []
+
+ for s in self._security_groups:
+ arglist.append(s.id)
+ verifylist = [
+ ('group', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for s in self._security_groups:
+ calls.append(call(s.id))
+ self.compute.security_groups.delete.assert_has_calls(calls)
self.assertIsNone(result)
+ def test_multi_security_groups_delete_with_exception(self):
+ arglist = [
+ self._security_groups[0].id,
+ 'unexist_security_group',
+ ]
+ verifylist = [
+ ('group',
+ [self._security_groups[0].id, 'unexist_security_group']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self._security_groups[0], exceptions.CommandError]
+ self.compute.security_groups.get = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+ self.compute.security_groups.find.side_effect = (
+ exceptions.NotFound(None))
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 groups failed to delete.', str(e))
+
+ self.compute.security_groups.get.assert_any_call(
+ self._security_groups[0].id)
+ self.compute.security_groups.get.assert_any_call(
+ 'unexist_security_group')
+ self.compute.security_groups.delete.assert_called_once_with(
+ self._security_groups[0].id
+ )
+
class TestListSecurityGroupNetwork(TestSecurityGroupNetwork):
diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py
index 2a64b88..170989b 100644
--- a/openstackclient/tests/network/v2/test_security_group_rule.py
+++ b/openstackclient/tests/network/v2/test_security_group_rule.py
@@ -13,8 +13,10 @@
import copy
import mock
+from mock import call
+
+from osc_lib import exceptions
-from openstackclient.common import exceptions
from openstackclient.network import utils as network_utils
from openstackclient.network.v2 import security_group_rule
from openstackclient.tests.compute.v2 import fakes as compute_fakes
@@ -31,6 +33,10 @@ class TestSecurityGroupRuleNetwork(network_fakes.TestNetworkV2):
# Get a shortcut to the network client
self.network = self.app.client_manager.network
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+ # Get a shortcut to the DomainManager Mock
+ self.domains_mock = self.app.client_manager.identity.domains
class TestSecurityGroupRuleCompute(compute_fakes.TestComputev2):
@@ -44,6 +50,8 @@ class TestSecurityGroupRuleCompute(compute_fakes.TestComputev2):
class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
+ project = identity_fakes.FakeProject.create_one_project()
+ domain = identity_fakes.FakeDomain.create_one_domain()
# The security group rule to be created.
_security_group_rule = None
@@ -91,29 +99,8 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.network.find_security_group = mock.Mock(
return_value=self._security_group)
- # Set identity client v3. And get a shortcut to Identity client.
- identity_client = identity_fakes.FakeIdentityv3Client(
- endpoint=fakes.AUTH_URL,
- token=fakes.AUTH_TOKEN,
- )
- self.app.client_manager.identity = identity_client
- self.identity = self.app.client_manager.identity
-
- # Get a shortcut to the ProjectManager Mock
- self.projects_mock = self.identity.projects
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
-
- # Get a shortcut to the DomainManager Mock
- self.domains_mock = self.identity.domains
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.DOMAIN),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
+ self.domains_mock.get.return_value = self.domain
# Get the command object to test
self.cmd = security_group_rule.CreateSecurityGroupRule(
@@ -304,8 +291,8 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
'--dst-port', str(self._security_group_rule.port_range_min),
'--egress',
'--ethertype', self._security_group_rule.ethertype,
- '--project', identity_fakes.project_name,
- '--project-domain', identity_fakes.domain_name,
+ '--project', self.project.name,
+ '--project-domain', self.domain.name,
'--protocol', self._security_group_rule.protocol,
self._security_group.id,
]
@@ -314,8 +301,8 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self._security_group_rule.port_range_max)),
('egress', True),
('ethertype', self._security_group_rule.ethertype),
- ('project', identity_fakes.project_name),
- ('project_domain', identity_fakes.domain_name),
+ ('project', self.project.name),
+ ('project_domain', self.domain.name),
('protocol', self._security_group_rule.protocol),
('group', self._security_group.id),
]
@@ -330,7 +317,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
'port_range_min': self._security_group_rule.port_range_min,
'protocol': self._security_group_rule.protocol,
'security_group_id': self._security_group.id,
- 'tenant_id': identity_fakes.project_id,
+ 'tenant_id': self.project.id,
})
self.assertEqual(self.expected_columns, columns)
self.assertEqual(self.expected_data, data)
@@ -468,6 +455,8 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
+ project = identity_fakes.FakeProject.create_one_project()
+ domain = identity_fakes.FakeDomain.create_one_domain()
# The security group rule to be created.
_security_group_rule = None
@@ -532,8 +521,8 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
'--ethertype', 'IPv4',
'--icmp-type', '3',
'--icmp-code', '11',
- '--project', identity_fakes.project_name,
- '--project-domain', identity_fakes.domain_name,
+ '--project', self.project.name,
+ '--project-domain', self.domain.name,
self._security_group.id,
]
self.assertRaises(tests_utils.ParserException,
@@ -667,17 +656,20 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
class TestDeleteSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
- # The security group rule to be deleted.
- _security_group_rule = \
- network_fakes.FakeSecurityGroupRule.create_one_security_group_rule()
+ # The security group rules to be deleted.
+ _security_group_rules = \
+ network_fakes.FakeSecurityGroupRule.create_security_group_rules(
+ count=2)
def setUp(self):
super(TestDeleteSecurityGroupRuleNetwork, self).setUp()
self.network.delete_security_group_rule = mock.Mock(return_value=None)
- self.network.find_security_group_rule = mock.Mock(
- return_value=self._security_group_rule)
+ self.network.find_security_group_rule = (
+ network_fakes.FakeSecurityGroupRule.get_security_group_rules(
+ self._security_group_rules)
+ )
# Get the command object to test
self.cmd = security_group_rule.DeleteSecurityGroupRule(
@@ -685,25 +677,76 @@ class TestDeleteSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
def test_security_group_rule_delete(self):
arglist = [
- self._security_group_rule.id,
+ self._security_group_rules[0].id,
]
verifylist = [
- ('rule', self._security_group_rule.id),
+ ('rule', [self._security_group_rules[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.network.delete_security_group_rule.assert_called_once_with(
- self._security_group_rule)
+ self._security_group_rules[0])
+ self.assertIsNone(result)
+
+ def test_multi_security_group_rules_delete(self):
+ arglist = []
+ verifylist = []
+
+ for s in self._security_group_rules:
+ arglist.append(s.id)
+ verifylist = [
+ ('rule', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for s in self._security_group_rules:
+ calls.append(call(s))
+ self.network.delete_security_group_rule.assert_has_calls(calls)
self.assertIsNone(result)
+ def test_multi_security_group_rules_delete_with_exception(self):
+ arglist = [
+ self._security_group_rules[0].id,
+ 'unexist_rule',
+ ]
+ verifylist = [
+ ('rule',
+ [self._security_group_rules[0].id, 'unexist_rule']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [
+ self._security_group_rules[0], exceptions.CommandError]
+ self.network.find_security_group_rule = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 rules failed to delete.', str(e))
+
+ self.network.find_security_group_rule.assert_any_call(
+ self._security_group_rules[0].id, ignore_missing=False)
+ self.network.find_security_group_rule.assert_any_call(
+ 'unexist_rule', ignore_missing=False)
+ self.network.delete_security_group_rule.assert_called_once_with(
+ self._security_group_rules[0]
+ )
+
class TestDeleteSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
# The security group rule to be deleted.
- _security_group_rule = \
- compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule()
+ _security_group_rules = \
+ compute_fakes.FakeSecurityGroupRule.create_security_group_rules(
+ count=2)
def setUp(self):
super(TestDeleteSecurityGroupRuleCompute, self).setUp()
@@ -715,19 +758,65 @@ class TestDeleteSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
def test_security_group_rule_delete(self):
arglist = [
- self._security_group_rule.id,
+ self._security_group_rules[0].id,
]
verifylist = [
- ('rule', self._security_group_rule.id),
+ ('rule', [self._security_group_rules[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.compute.security_group_rules.delete.assert_called_once_with(
- self._security_group_rule.id)
+ self._security_group_rules[0].id)
+ self.assertIsNone(result)
+
+ def test_multi_security_group_rules_delete(self):
+ arglist = []
+ verifylist = []
+
+ for s in self._security_group_rules:
+ arglist.append(s.id)
+ verifylist = [
+ ('rule', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for s in self._security_group_rules:
+ calls.append(call(s.id))
+ self.compute.security_group_rules.delete.assert_has_calls(calls)
self.assertIsNone(result)
+ def test_multi_security_group_rules_delete_with_exception(self):
+ arglist = [
+ self._security_group_rules[0].id,
+ 'unexist_rule',
+ ]
+ verifylist = [
+ ('rule',
+ [self._security_group_rules[0].id, 'unexist_rule']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [None, exceptions.CommandError]
+ self.compute.security_group_rules.delete = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 rules failed to delete.', str(e))
+
+ self.compute.security_group_rules.delete.assert_any_call(
+ self._security_group_rules[0].id)
+ self.compute.security_group_rules.delete.assert_any_call(
+ 'unexist_rule')
+
class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py
index 22c288f..ba757c9 100644
--- a/openstackclient/tests/network/v2/test_subnet.py
+++ b/openstackclient/tests/network/v2/test_subnet.py
@@ -11,13 +11,13 @@
# under the License.
#
-import copy
import mock
+from mock import call
+
+from osc_lib import exceptions
+from osc_lib import utils
-from openstackclient.common import exceptions
-from openstackclient.common import utils
from openstackclient.network.v2 import subnet as subnet_v2
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3
from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils as tests_utils
@@ -30,14 +30,20 @@ class TestSubnet(network_fakes.TestNetworkV2):
# Get a shortcut to the network client
self.network = self.app.client_manager.network
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+ # Get a shortcut to the DomainManager Mock
+ self.domains_mock = self.app.client_manager.identity.domains
class TestCreateSubnet(TestSubnet):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ domain = identity_fakes_v3.FakeDomain.create_one_domain()
# An IPv4 subnet to be created with mostly default values
_subnet = network_fakes.FakeSubnet.create_one_subnet(
attrs={
- 'tenant_id': identity_fakes_v3.project_id,
+ 'tenant_id': project.id,
}
)
@@ -47,7 +53,7 @@ class TestCreateSubnet(TestSubnet):
# An IPv4 subnet to be created using a specific subnet pool
_subnet_from_pool = network_fakes.FakeSubnet.create_one_subnet(
attrs={
- 'tenant_id': identity_fakes_v3.project_id,
+ 'tenant_id': project.id,
'subnetpool_id': _subnet_pool.id,
'dns_nameservers': ['8.8.8.8',
'8.8.4.4'],
@@ -61,7 +67,7 @@ class TestCreateSubnet(TestSubnet):
# An IPv6 subnet to be created with most options specified
_subnet_ipv6 = network_fakes.FakeSubnet.create_one_subnet(
attrs={
- 'tenant_id': identity_fakes_v3.project_id,
+ 'tenant_id': project.id,
'cidr': 'fe80:0:0:a00a::/64',
'enable_dhcp': True,
'dns_nameservers': ['fe80:27ff:a00a:f00f::ffff',
@@ -89,6 +95,14 @@ class TestCreateSubnet(TestSubnet):
}
)
+ # The network segment to be returned from find_segment
+ _network_segment = \
+ network_fakes.FakeNetworkSegment.create_one_network_segment(
+ attrs={
+ 'network_id': _subnet.network_id,
+ }
+ )
+
columns = (
'allocation_pools',
'cidr',
@@ -103,6 +117,7 @@ class TestCreateSubnet(TestSubnet):
'name',
'network_id',
'project_id',
+ 'segment_id',
'subnetpool_id',
)
@@ -120,6 +135,7 @@ class TestCreateSubnet(TestSubnet):
_subnet.name,
_subnet.network_id,
_subnet.project_id,
+ _subnet.segment_id,
_subnet.subnetpool_id,
)
@@ -137,6 +153,7 @@ class TestCreateSubnet(TestSubnet):
_subnet_from_pool.name,
_subnet_from_pool.network_id,
_subnet_from_pool.project_id,
+ _subnet_from_pool.segment_id,
_subnet_from_pool.subnetpool_id,
)
@@ -154,6 +171,7 @@ class TestCreateSubnet(TestSubnet):
_subnet_ipv6.name,
_subnet_ipv6.network_id,
_subnet_ipv6.project_id,
+ _subnet_ipv6.segment_id,
_subnet_ipv6.subnetpool_id,
)
@@ -163,28 +181,16 @@ class TestCreateSubnet(TestSubnet):
# Get the command object to test
self.cmd = subnet_v2.CreateSubnet(self.app, self.namespace)
- # Set identity client v3. And get a shortcut to Identity client.
- identity_client = identity_fakes_v3.FakeIdentityv3Client(
- endpoint=fakes.AUTH_URL,
- token=fakes.AUTH_TOKEN,
- )
- self.app.client_manager.identity = identity_client
- self.identity = self.app.client_manager.identity
+ self.projects_mock.get.return_value = self.project
+ self.domains_mock.get.return_value = self.domain
- # Get a shortcut to the ProjectManager Mock
- self.projects_mock = self.identity.projects
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes_v3.PROJECT),
- loaded=True,
+ # Mock SDK calls for all tests.
+ self.network.find_network = mock.Mock(return_value=self._network)
+ self.network.find_segment = mock.Mock(
+ return_value=self._network_segment
)
-
- # Get a shortcut to the DomainManager Mock
- self.domains_mock = self.identity.domains
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes_v3.DOMAIN),
- loaded=True,
+ self.network.find_subnet_pool = mock.Mock(
+ return_value=self._subnet_pool
)
def test_create_no_options(self):
@@ -197,11 +203,9 @@ class TestCreateSubnet(TestSubnet):
self.check_parser, self.cmd, arglist, verifylist)
def test_create_default_options(self):
- # Mock create_subnet and find_network sdk calls to return the
- # values we want for this test
+ # Mock SDK calls for this test.
self.network.create_subnet = mock.Mock(return_value=self._subnet)
self._network.id = self._subnet.network_id
- self.network.find_network = mock.Mock(return_value=self._network)
arglist = [
"--subnet-range", self._subnet.cidr,
@@ -222,7 +226,6 @@ class TestCreateSubnet(TestSubnet):
self.network.create_subnet.assert_called_once_with(**{
'cidr': self._subnet.cidr,
- 'enable_dhcp': self._subnet.enable_dhcp,
'ip_version': self._subnet.ip_version,
'name': self._subnet.name,
'network_id': self._subnet.network_id,
@@ -231,14 +234,10 @@ class TestCreateSubnet(TestSubnet):
self.assertEqual(self.data, data)
def test_create_from_subnet_pool_options(self):
- # Mock create_subnet, find_subnet_pool, and find_network sdk calls
- # to return the values we want for this test
+ # Mock SDK calls for this test.
self.network.create_subnet = \
mock.Mock(return_value=self._subnet_from_pool)
self._network.id = self._subnet_from_pool.network_id
- self.network.find_network = mock.Mock(return_value=self._network)
- self.network.find_subnet_pool = \
- mock.Mock(return_value=self._subnet_pool)
arglist = [
self._subnet_from_pool.name,
@@ -291,11 +290,9 @@ class TestCreateSubnet(TestSubnet):
self.assertEqual(self.data_subnet_pool, data)
def test_create_options_subnet_range_ipv6(self):
- # Mock create_subnet and find_network sdk calls to return the
- # values we want for this test
+ # Mock SDK calls for this test.
self.network.create_subnet = mock.Mock(return_value=self._subnet_ipv6)
self._network.id = self._subnet_ipv6.network_id
- self.network.find_network = mock.Mock(return_value=self._network)
arglist = [
self._subnet_ipv6.name,
@@ -358,35 +355,137 @@ class TestCreateSubnet(TestSubnet):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data_ipv6, data)
+ def test_create_no_beta_command_options(self):
+ arglist = [
+ "--subnet-range", self._subnet.cidr,
+ "--network-segment", self._network_segment.id,
+ "--network", self._subnet.network_id,
+ self._subnet.name,
+ ]
+ verifylist = [
+ ('name', self._subnet.name),
+ ('subnet_range', self._subnet.cidr),
+ ('network-segment', self._network_segment.id),
+ ('network', self._subnet.network_id),
+ ]
+ self.app.options.os_beta_command = False
+ self.assertRaises(tests_utils.ParserException,
+ self.check_parser, self.cmd, arglist, verifylist)
+
+ def test_create_with_network_segment(self):
+ # Mock SDK calls for this test.
+ self.network.create_subnet = mock.Mock(return_value=self._subnet)
+ self._network.id = self._subnet.network_id
+
+ arglist = [
+ "--subnet-range", self._subnet.cidr,
+ "--network-segment", self._network_segment.id,
+ "--network", self._subnet.network_id,
+ self._subnet.name,
+ ]
+ verifylist = [
+ ('name', self._subnet.name),
+ ('subnet_range', self._subnet.cidr),
+ ('network_segment', self._network_segment.id),
+ ('network', self._subnet.network_id),
+ ('ip_version', self._subnet.ip_version),
+ ('gateway', 'auto'),
+
+ ]
+
+ self.app.options.os_beta_command = True
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_subnet.assert_called_once_with(**{
+ 'cidr': self._subnet.cidr,
+ 'ip_version': self._subnet.ip_version,
+ 'name': self._subnet.name,
+ 'network_id': self._subnet.network_id,
+ 'segment_id': self._network_segment.id,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
class TestDeleteSubnet(TestSubnet):
- # The subnet to delete.
- _subnet = network_fakes.FakeSubnet.create_one_subnet()
+ # The subnets to delete.
+ _subnets = network_fakes.FakeSubnet.create_subnets(count=2)
def setUp(self):
super(TestDeleteSubnet, self).setUp()
self.network.delete_subnet = mock.Mock(return_value=None)
- self.network.find_subnet = mock.Mock(return_value=self._subnet)
+ self.network.find_subnet = (
+ network_fakes.FakeSubnet.get_subnets(self._subnets))
# Get the command object to test
self.cmd = subnet_v2.DeleteSubnet(self.app, self.namespace)
- def test_delete(self):
+ def test_subnet_delete(self):
arglist = [
- self._subnet.name,
+ self._subnets[0].name,
]
verifylist = [
- ('subnet', self._subnet.name),
+ ('subnet', [self._subnets[0].name]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.delete_subnet.assert_called_once_with(self._subnets[0])
+ self.assertIsNone(result)
+
+ def test_multi_subnets_delete(self):
+ arglist = []
+ verifylist = []
+
+ for s in self._subnets:
+ arglist.append(s.name)
+ verifylist = [
+ ('subnet', arglist),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.network.delete_subnet.assert_called_once_with(self._subnet)
+
+ calls = []
+ for s in self._subnets:
+ calls.append(call(s))
+ self.network.delete_subnet.assert_has_calls(calls)
self.assertIsNone(result)
+ def test_multi_subnets_delete_with_exception(self):
+ arglist = [
+ self._subnets[0].name,
+ 'unexist_subnet',
+ ]
+ verifylist = [
+ ('subnet',
+ [self._subnets[0].name, 'unexist_subnet']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self._subnets[0], exceptions.CommandError]
+ self.network.find_subnet = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 subnets failed to delete.', str(e))
+
+ self.network.find_subnet.assert_any_call(
+ self._subnets[0].name, ignore_missing=False)
+ self.network.find_subnet.assert_any_call(
+ 'unexist_subnet', ignore_missing=False)
+ self.network.delete_subnet.assert_called_once_with(
+ self._subnets[0]
+ )
+
class TestListSubnet(TestSubnet):
# The subnets going to be listed up.
@@ -485,6 +584,38 @@ class TestListSubnet(TestSubnet):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_subnet_list_dhcp(self):
+ arglist = [
+ '--dhcp',
+ ]
+ verifylist = [
+ ('dhcp', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ filters = {'enable_dhcp': True}
+
+ self.network.subnets.assert_called_once_with(**filters)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_subnet_list_no_dhcp(self):
+ arglist = [
+ '--no-dhcp',
+ ]
+ verifylist = [
+ ('no_dhcp', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ filters = {'enable_dhcp': False}
+
+ self.network.subnets.assert_called_once_with(**filters)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
class TestSetSubnet(TestSubnet):
@@ -549,8 +680,11 @@ class TestSetSubnet(TestSubnet):
verifylist = [('subnet', self._subnet.name)]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.assertRaises(exceptions.CommandError, self.cmd.take_action,
- parsed_args)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {}
+ self.network.update_subnet.assert_called_with(self._subnet, **attrs)
+ self.assertIsNone(result)
def test_append_options(self):
_testsubnet = network_fakes.FakeSubnet.create_one_subnet(
@@ -591,6 +725,7 @@ class TestShowSubnet(TestSubnet):
'name',
'network_id',
'project_id',
+ 'segment_id',
'subnetpool_id',
)
@@ -608,6 +743,7 @@ class TestShowSubnet(TestSubnet):
_subnet.name,
_subnet.network_id,
_subnet.tenant_id,
+ _subnet.segment_id,
_subnet.subnetpool_id,
)
@@ -644,3 +780,109 @@ class TestShowSubnet(TestSubnet):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+
+
+class TestUnsetSubnet(TestSubnet):
+
+ def setUp(self):
+ super(TestUnsetSubnet, self).setUp()
+ self._testsubnet = network_fakes.FakeSubnet.create_one_subnet(
+ {'dns_nameservers': ['8.8.8.8',
+ '8.8.8.4'],
+ 'host_routes': [{'destination': '10.20.20.0/24',
+ 'nexthop': '10.20.20.1'},
+ {'destination': '10.30.30.30/24',
+ 'nexthop': '10.30.30.1'}],
+ 'allocation_pools': [{'start': '8.8.8.100',
+ 'end': '8.8.8.150'},
+ {'start': '8.8.8.160',
+ 'end': '8.8.8.170'}], })
+ self.network.find_subnet = mock.Mock(return_value=self._testsubnet)
+ self.network.update_subnet = mock.Mock(return_value=None)
+ # Get the command object to test
+ self.cmd = subnet_v2.UnsetSubnet(self.app, self.namespace)
+
+ def test_unset_subnet_params(self):
+ arglist = [
+ '--dns-nameserver', '8.8.8.8',
+ '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1',
+ '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150',
+ self._testsubnet.name,
+ ]
+ verifylist = [
+ ('dns_nameservers', ['8.8.8.8']),
+ ('host_routes', [{
+ "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]),
+ ('allocation_pools', [{
+ 'start': '8.8.8.100', 'end': '8.8.8.150'}]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'dns_nameservers': ['8.8.8.4'],
+ 'host_routes': [{
+ "destination": "10.20.20.0/24", "nexthop": "10.20.20.1"}],
+ 'allocation_pools': [{'start': '8.8.8.160', 'end': '8.8.8.170'}],
+ }
+ self.network.update_subnet.assert_called_once_with(
+ self._testsubnet, **attrs)
+ self.assertIsNone(result)
+
+ def test_unset_subnet_wrong_host_routes(self):
+ arglist = [
+ '--dns-nameserver', '8.8.8.8',
+ '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.2',
+ '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150',
+ self._testsubnet.name,
+ ]
+ verifylist = [
+ ('dns_nameservers', ['8.8.8.8']),
+ ('host_routes', [{
+ "destination": "10.30.30.30/24", "gateway": "10.30.30.2"}]),
+ ('allocation_pools', [{
+ 'start': '8.8.8.100', 'end': '8.8.8.150'}]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
+
+ def test_unset_subnet_wrong_allocation_pool(self):
+ arglist = [
+ '--dns-nameserver', '8.8.8.8',
+ '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1',
+ '--allocation-pool', 'start=8.8.8.100,end=8.8.8.156',
+ self._testsubnet.name,
+ ]
+ verifylist = [
+ ('dns_nameservers', ['8.8.8.8']),
+ ('host_routes', [{
+ "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]),
+ ('allocation_pools', [{
+ 'start': '8.8.8.100', 'end': '8.8.8.156'}]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
+
+ def test_unset_subnet_wrong_dns_nameservers(self):
+ arglist = [
+ '--dns-nameserver', '8.8.8.1',
+ '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1',
+ '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150',
+ self._testsubnet.name,
+ ]
+ verifylist = [
+ ('dns_nameservers', ['8.8.8.1']),
+ ('host_routes', [{
+ "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]),
+ ('allocation_pools', [{
+ 'start': '8.8.8.100', 'end': '8.8.8.150'}]),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py
index de12c9e..4cfecef 100644
--- a/openstackclient/tests/network/v2/test_subnet_pool.py
+++ b/openstackclient/tests/network/v2/test_subnet_pool.py
@@ -12,13 +12,13 @@
#
import argparse
-import copy
import mock
+from mock import call
+
+from osc_lib import exceptions
+from osc_lib import utils
-from openstackclient.common import exceptions
-from openstackclient.common import utils
from openstackclient.network.v2 import subnet_pool
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3
from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils as tests_utils
@@ -31,10 +31,16 @@ class TestSubnetPool(network_fakes.TestNetworkV2):
# Get a shortcut to the network client
self.network = self.app.client_manager.network
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+ # Get a shortcut to the DomainManager Mock
+ self.domains_mock = self.app.client_manager.identity.domains
class TestCreateSubnetPool(TestSubnetPool):
+ project = identity_fakes_v3.FakeProject.create_one_project()
+ domain = identity_fakes_v3.FakeDomain.create_one_domain()
# The new subnet pool to create.
_subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool()
@@ -81,29 +87,8 @@ class TestCreateSubnetPool(TestSubnetPool):
self.network.find_address_scope = mock.Mock(
return_value=self._address_scope)
- # Set identity client. And get a shortcut to Identity client.
- identity_client = identity_fakes_v3.FakeIdentityv3Client(
- endpoint=fakes.AUTH_URL,
- token=fakes.AUTH_TOKEN,
- )
- self.app.client_manager.identity = identity_client
- self.identity = self.app.client_manager.identity
-
- # Get a shortcut to the ProjectManager Mock
- self.projects_mock = self.identity.projects
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes_v3.PROJECT),
- loaded=True,
- )
-
- # Get a shortcut to the DomainManager Mock
- self.domains_mock = self.identity.domains
- self.domains_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes_v3.DOMAIN),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
+ self.domains_mock.get.return_value = self.domain
def test_create_no_options(self):
arglist = []
@@ -189,14 +174,14 @@ class TestCreateSubnetPool(TestSubnetPool):
def test_create_project_domain(self):
arglist = [
'--pool-prefix', '10.0.10.0/24',
- "--project", identity_fakes_v3.project_name,
- "--project-domain", identity_fakes_v3.domain_name,
+ "--project", self.project.name,
+ "--project-domain", self.domain.name,
self._subnet_pool.name,
]
verifylist = [
('prefixes', ['10.0.10.0/24']),
- ('project', identity_fakes_v3.project_name),
- ('project_domain', identity_fakes_v3.domain_name),
+ ('project', self.project.name),
+ ('project_domain', self.domain.name),
('name', self._subnet_pool.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -205,7 +190,7 @@ class TestCreateSubnetPool(TestSubnetPool):
self.network.create_subnet_pool.assert_called_once_with(**{
'prefixes': ['10.0.10.0/24'],
- 'tenant_id': identity_fakes_v3.project_id,
+ 'tenant_id': self.project.id,
'name': self._subnet_pool.name,
})
self.assertEqual(self.columns, columns)
@@ -263,36 +248,85 @@ class TestCreateSubnetPool(TestSubnetPool):
class TestDeleteSubnetPool(TestSubnetPool):
- # The subnet pool to delete.
- _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool()
+ # The subnet pools to delete.
+ _subnet_pools = network_fakes.FakeSubnetPool.create_subnet_pools(count=2)
def setUp(self):
super(TestDeleteSubnetPool, self).setUp()
self.network.delete_subnet_pool = mock.Mock(return_value=None)
- self.network.find_subnet_pool = mock.Mock(
- return_value=self._subnet_pool
+ self.network.find_subnet_pool = (
+ network_fakes.FakeSubnetPool.get_subnet_pools(self._subnet_pools)
)
# Get the command object to test
self.cmd = subnet_pool.DeleteSubnetPool(self.app, self.namespace)
- def test_delete(self):
+ def test_subnet_pool_delete(self):
arglist = [
- self._subnet_pool.name,
+ self._subnet_pools[0].name,
]
verifylist = [
- ('subnet_pool', self._subnet_pool.name),
+ ('subnet_pool', [self._subnet_pools[0].name]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.network.delete_subnet_pool.assert_called_once_with(
- self._subnet_pool)
+ self._subnet_pools[0])
+ self.assertIsNone(result)
+
+ def test_multi_subnet_pools_delete(self):
+ arglist = []
+ verifylist = []
+
+ for s in self._subnet_pools:
+ arglist.append(s.name)
+ verifylist = [
+ ('subnet_pool', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for s in self._subnet_pools:
+ calls.append(call(s))
+ self.network.delete_subnet_pool.assert_has_calls(calls)
self.assertIsNone(result)
+ def test_multi_subnet_pools_delete_with_exception(self):
+ arglist = [
+ self._subnet_pools[0].name,
+ 'unexist_subnet_pool',
+ ]
+ verifylist = [
+ ('subnet_pool',
+ [self._subnet_pools[0].name, 'unexist_subnet_pool']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self._subnet_pools[0], exceptions.CommandError]
+ self.network.find_subnet_pool = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 subnet pools failed to delete.', str(e))
+
+ self.network.find_subnet_pool.assert_any_call(
+ self._subnet_pools[0].name, ignore_missing=False)
+ self.network.find_subnet_pool.assert_any_call(
+ 'unexist_subnet_pool', ignore_missing=False)
+ self.network.delete_subnet_pool.assert_called_once_with(
+ self._subnet_pools[0]
+ )
+
class TestListSubnetPool(TestSubnetPool):
# The subnet pools going to be listed up.
@@ -443,10 +477,14 @@ class TestSetSubnetPool(TestSubnetPool):
def test_set_nothing(self):
arglist = [self._subnet_pool.name, ]
verifylist = [('subnet_pool', self._subnet_pool.name), ]
+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
- self.assertRaises(exceptions.CommandError, self.cmd.take_action,
- parsed_args)
+ attrs = {}
+ self.network.update_subnet_pool.assert_called_once_with(
+ self._subnet_pool, **attrs)
+ self.assertIsNone(result)
def test_set_len_negative(self):
arglist = [
@@ -643,3 +681,42 @@ class TestShowSubnetPool(TestSubnetPool):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+
+
+class TestUnsetSubnetPool(TestSubnetPool):
+
+ def setUp(self):
+ super(TestUnsetSubnetPool, self).setUp()
+ self._subnetpool = network_fakes.FakeSubnetPool.create_one_subnet_pool(
+ {'prefixes': ['10.0.10.0/24', '10.1.10.0/24',
+ '10.2.10.0/24'], })
+ self.network.find_subnet_pool = mock.Mock(
+ return_value=self._subnetpool)
+ self.network.update_subnet_pool = mock.Mock(return_value=None)
+ # Get the command object to test
+ self.cmd = subnet_pool.UnsetSubnetPool(self.app, self.namespace)
+
+ def test_unset_subnet_pool(self):
+ arglist = [
+ '--pool-prefix', '10.0.10.0/24',
+ '--pool-prefix', '10.1.10.0/24',
+ self._subnetpool.name,
+ ]
+ verifylist = [('prefixes', ['10.0.10.0/24', '10.1.10.0/24'])]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {'prefixes': ['10.2.10.0/24']}
+ self.network.update_subnet_pool.assert_called_once_with(
+ self._subnetpool, **attrs)
+ self.assertIsNone(result)
+
+ def test_unset_subnet_pool_prefix_not_existent(self):
+ arglist = [
+ '--pool-prefix', '10.100.1.1/25',
+ self._subnetpool.name,
+ ]
+ verifylist = [('prefixes', ['10.100.1.1/25'])]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
diff --git a/openstackclient/tests/object/v1/fakes.py b/openstackclient/tests/object/v1/fakes.py
index b9e86db..6c36711 100644
--- a/openstackclient/tests/object/v1/fakes.py
+++ b/openstackclient/tests/object/v1/fakes.py
@@ -14,6 +14,7 @@
#
from keystoneauth1 import session
+
from openstackclient.api import object_store_v1 as object_store
from openstackclient.tests import utils
diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py
index 90454fc..87cd7f5 100644
--- a/openstackclient/tests/test_shell.py
+++ b/openstackclient/tests/test_shell.py
@@ -1,4 +1,4 @@
-# Copyright 2012-2013 OpenStack, LLC.
+# Copyright 2012-2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -13,14 +13,15 @@
# under the License.
#
-import copy
-import fixtures
import mock
import os
-import testtools
+import sys
+
+from osc_lib.tests import utils as osc_lib_test_utils
+from oslo_utils import importutils
+import wrapt
from openstackclient import shell
-from openstackclient.tests import utils
DEFAULT_AUTH_URL = "http://127.0.0.1:5000/v2.0/"
@@ -116,173 +117,97 @@ global_options = {
'--os-interface': (DEFAULT_INTERFACE, True, True)
}
-auth_options = {
- '--os-auth-url': (DEFAULT_AUTH_URL, True, True),
- '--os-project-id': (DEFAULT_PROJECT_ID, True, True),
- '--os-project-name': (DEFAULT_PROJECT_NAME, True, True),
- '--os-domain-id': (DEFAULT_DOMAIN_ID, True, True),
- '--os-domain-name': (DEFAULT_DOMAIN_NAME, True, True),
- '--os-user-domain-id': (DEFAULT_USER_DOMAIN_ID, True, True),
- '--os-user-domain-name': (DEFAULT_USER_DOMAIN_NAME, True, True),
- '--os-project-domain-id': (DEFAULT_PROJECT_DOMAIN_ID, True, True),
- '--os-project-domain-name': (DEFAULT_PROJECT_DOMAIN_NAME, True, True),
- '--os-username': (DEFAULT_USERNAME, True, True),
- '--os-password': (DEFAULT_PASSWORD, True, True),
- '--os-region-name': (DEFAULT_REGION_NAME, True, True),
- '--os-trust-id': ("1234", True, True),
- '--os-auth-type': ("v2password", True, True),
- '--os-token': (DEFAULT_TOKEN, True, True),
- '--os-url': (DEFAULT_SERVICE_URL, True, True),
- '--os-interface': (DEFAULT_INTERFACE, True, True),
-}
-
-
-def opt2attr(opt):
- if opt.startswith('--os-'):
- attr = opt[5:]
- elif opt.startswith('--'):
- attr = opt[2:]
- else:
- attr = opt
- return attr.lower().replace('-', '_')
-
-
-def opt2env(opt):
- return opt[2:].upper().replace('-', '_')
-
-
-def make_shell():
- """Create a new command shell and mock out some bits."""
- _shell = shell.OpenStackShell()
- _shell.command_manager = mock.Mock()
-
- return _shell
+# Wrap the osc_lib make_shell() function to set the shell class since
+# osc-lib's TestShell class doesn't allow us to specify it yet.
+# TODO(dtroyer): remove this once the shell_class_patch patch is released
+# in osc-lib
+def make_shell_wrapper(func, inst, args, kwargs):
+ if 'shell_class' not in kwargs:
+ kwargs['shell_class'] = shell.OpenStackShell
+ return func(*args, **kwargs)
-def fake_execute(shell, cmd):
- """Pretend to execute shell commands."""
- return shell.run(cmd.split())
+wrapt.wrap_function_wrapper(
+ osc_lib_test_utils,
+ 'make_shell',
+ make_shell_wrapper,
+)
-class EnvFixture(fixtures.Fixture):
- """Environment Fixture.
- This fixture replaces os.environ with provided env or an empty env.
- """
+class TestShell(osc_lib_test_utils.TestShell):
- def __init__(self, env=None):
- self.new_env = env or {}
+ # Full name of the OpenStackShell class to test (cliff.app.App subclass)
+ shell_class_name = "openstackclient.shell.OpenStackShell"
- def _setUp(self):
- self.orig_env, os.environ = os.environ, self.new_env
- self.addCleanup(self.revert)
-
- def revert(self):
- os.environ = self.orig_env
-
-
-class TestShell(utils.TestCase):
+ # TODO(dtroyer): remove this once the shell_class_patch patch is released
+ # in osc-lib
+ app_patch = shell_class_name
def setUp(self):
super(TestShell, self).setUp()
- patch = "openstackclient.shell.OpenStackShell.run_subcommand"
- self.cmd_patch = mock.patch(patch)
- self.cmd_save = self.cmd_patch.start()
- self.addCleanup(self.cmd_patch.stop)
- self.app = mock.Mock("Test Shell")
-
- def _assert_initialize_app_arg(self, cmd_options, default_args):
- """Check the args passed to initialize_app()
-
- The argv argument to initialize_app() is the remainder from parsing
- global options declared in both cliff.app and
- openstackclient.OpenStackShell build_option_parser(). Any global
- options passed on the commmad line should not be in argv but in
- _shell.options.
- """
+ # TODO(dtroyer): remove this once the shell_class_patch patch is
+ # released in osc-lib
+ self.shell_class = importutils.import_class(self.shell_class_name)
+ def _assert_token_endpoint_auth(self, cmd_options, default_args):
with mock.patch(
- "openstackclient.shell.OpenStackShell.initialize_app",
+ self.shell_class_name + ".initialize_app",
self.app,
):
- _shell, _cmd = make_shell(), cmd_options + " list project"
- fake_execute(_shell, _cmd)
-
- self.app.assert_called_with(["list", "project"])
- for k in default_args.keys():
- self.assertEqual(
- default_args[k],
- vars(_shell.options)[k],
- "%s does not match" % k,
- )
-
- def _assert_cloud_config_arg(self, cmd_options, default_args):
- """Check the args passed to cloud_config.get_one_cloud()
-
- The argparse argument to get_one_cloud() is an argparse.Namespace
- object that contains all of the options processed to this point in
- initialize_app().
- """
-
- cloud = mock.Mock(name="cloudy")
- cloud.config = {}
- self.occ_get_one = mock.Mock(return_value=cloud)
- with mock.patch(
- "os_client_config.config.OpenStackConfig.get_one_cloud",
- self.occ_get_one,
- ):
- _shell, _cmd = make_shell(), cmd_options + " list project"
- fake_execute(_shell, _cmd)
-
- opts = self.occ_get_one.call_args[1]['argparse']
- for k in default_args.keys():
- self.assertEqual(
- default_args[k],
- vars(opts)[k],
- "%s does not match" % k,
- )
-
- def _assert_token_auth(self, cmd_options, default_args):
- with mock.patch("openstackclient.shell.OpenStackShell.initialize_app",
- self.app):
- _shell, _cmd = make_shell(), cmd_options + " list role"
- fake_execute(_shell, _cmd)
+ _shell = osc_lib_test_utils.make_shell(
+ shell_class=self.shell_class,
+ )
+ _cmd = cmd_options + " list role"
+ osc_lib_test_utils.fake_execute(_shell, _cmd)
+ print("_shell: %s" % _shell)
self.app.assert_called_with(["list", "role"])
self.assertEqual(
default_args.get("token", ''),
_shell.options.token,
- "token"
+ "token",
)
self.assertEqual(
- default_args.get("auth_url", ''),
- _shell.options.auth_url,
- "auth_url"
+ default_args.get("url", ''),
+ _shell.options.url,
+ "url",
)
- def _assert_token_endpoint_auth(self, cmd_options, default_args):
- with mock.patch("openstackclient.shell.OpenStackShell.initialize_app",
- self.app):
- _shell, _cmd = make_shell(), cmd_options + " list role"
- fake_execute(_shell, _cmd)
+ def _assert_token_auth(self, cmd_options, default_args):
+ with mock.patch(
+ self.app_patch + ".initialize_app",
+ self.app,
+ ):
+ _shell = osc_lib_test_utils.make_shell(
+ shell_class=self.shell_class,
+ )
+ _cmd = cmd_options + " list role"
+ osc_lib_test_utils.fake_execute(_shell, _cmd)
+ print("_shell: %s" % _shell)
self.app.assert_called_with(["list", "role"])
self.assertEqual(
default_args.get("token", ''),
_shell.options.token,
- "token",
+ "token"
)
self.assertEqual(
- default_args.get("url", ''),
- _shell.options.url,
- "url",
+ default_args.get("auth_url", ''),
+ _shell.options.auth_url,
+ "auth_url"
)
def _assert_cli(self, cmd_options, default_args):
- with mock.patch("openstackclient.shell.OpenStackShell.initialize_app",
- self.app):
- _shell, _cmd = make_shell(), cmd_options + " list server"
- fake_execute(_shell, _cmd)
+ with mock.patch(
+ self.shell_class_name + ".initialize_app",
+ self.app,
+ ):
+ _shell = osc_lib_test_utils.make_shell(
+ shell_class=self.shell_class,
+ )
+ _cmd = cmd_options + " list server"
+ osc_lib_test_utils.fake_execute(_shell, _cmd)
self.app.assert_called_with(["list", "server"])
self.assertEqual(default_args["compute_api_version"],
@@ -297,39 +222,17 @@ class TestShell(utils.TestCase):
_shell.options.os_network_api_version)
-class TestShellHelp(TestShell):
- """Test the deferred help flag"""
-
- def setUp(self):
- super(TestShellHelp, self).setUp()
- self.useFixture(EnvFixture())
-
- @testtools.skip("skip until bug 1444983 is resolved")
- def test_help_options(self):
- flag = "-h list server"
- kwargs = {
- "deferred_help": True,
- }
- with mock.patch("openstackclient.shell.OpenStackShell.initialize_app",
- self.app):
- _shell, _cmd = make_shell(), flag
- fake_execute(_shell, _cmd)
-
- self.assertEqual(kwargs["deferred_help"],
- _shell.options.deferred_help)
-
-
class TestShellOptions(TestShell):
def setUp(self):
super(TestShellOptions, self).setUp()
- self.useFixture(EnvFixture())
+ self.useFixture(osc_lib_test_utils.EnvFixture())
def _test_options_init_app(self, test_opts):
for opt in test_opts.keys():
if not test_opts[opt][1]:
continue
- key = opt2attr(opt)
+ key = osc_lib_test_utils.opt2attr(opt)
if isinstance(test_opts[opt][0], str):
cmd = opt + " " + test_opts[opt][0]
else:
@@ -343,7 +246,7 @@ class TestShellOptions(TestShell):
for opt in test_opts.keys():
if not test_opts[opt][1]:
continue
- key = opt2attr(opt)
+ key = osc_lib_test_utils.opt2attr(opt)
if isinstance(test_opts[opt][0], str):
cmd = opt + " " + test_opts[opt][0]
else:
@@ -357,12 +260,12 @@ class TestShellOptions(TestShell):
for opt in test_opts.keys():
if not test_opts[opt][2]:
continue
- key = opt2attr(opt)
+ key = osc_lib_test_utils.opt2attr(opt)
kwargs = {
key: test_opts[opt][0],
}
env = {
- opt2env(opt): test_opts[opt][0],
+ osc_lib_test_utils.opt2env(opt): test_opts[opt][0],
}
os.environ = env.copy()
self._assert_initialize_app_arg("", kwargs)
@@ -371,37 +274,16 @@ class TestShellOptions(TestShell):
for opt in test_opts.keys():
if not test_opts[opt][2]:
continue
- key = opt2attr(opt)
+ key = osc_lib_test_utils.opt2attr(opt)
kwargs = {
key: test_opts[opt][0],
}
env = {
- opt2env(opt): test_opts[opt][0],
+ osc_lib_test_utils.opt2env(opt): test_opts[opt][0],
}
os.environ = env.copy()
self._assert_cloud_config_arg("", kwargs)
- def test_empty_auth(self):
- os.environ = {}
- self._assert_initialize_app_arg("", {})
- self._assert_cloud_config_arg("", {})
-
- def test_global_options(self):
- self._test_options_init_app(global_options)
- self._test_options_get_one_cloud(global_options)
-
- def test_auth_options(self):
- self._test_options_init_app(auth_options)
- self._test_options_get_one_cloud(auth_options)
-
- def test_global_env(self):
- self._test_env_init_app(global_options)
- self._test_env_get_one_cloud(global_options)
-
- def test_auth_env(self):
- self._test_env_init_app(auth_options)
- self._test_env_get_one_cloud(auth_options)
-
class TestShellTokenAuthEnv(TestShell):
@@ -411,7 +293,7 @@ class TestShellTokenAuthEnv(TestShell):
"OS_TOKEN": DEFAULT_TOKEN,
"OS_AUTH_URL": DEFAULT_AUTH_URL,
}
- self.useFixture(EnvFixture(env.copy()))
+ self.useFixture(osc_lib_test_utils.EnvFixture(env.copy()))
def test_env(self):
flag = ""
@@ -455,7 +337,7 @@ class TestShellTokenEndpointAuthEnv(TestShell):
"OS_TOKEN": DEFAULT_TOKEN,
"OS_URL": DEFAULT_SERVICE_URL,
}
- self.useFixture(EnvFixture(env.copy()))
+ self.useFixture(osc_lib_test_utils.EnvFixture(env.copy()))
def test_env(self):
flag = ""
@@ -463,7 +345,7 @@ class TestShellTokenEndpointAuthEnv(TestShell):
"token": DEFAULT_TOKEN,
"url": DEFAULT_SERVICE_URL,
}
- self._assert_token_auth(flag, kwargs)
+ self._assert_token_endpoint_auth(flag, kwargs)
def test_only_token(self):
flag = "--os-token xyzpdq"
@@ -502,85 +384,7 @@ class TestShellCli(TestShell):
"OS_VOLUME_API_VERSION": DEFAULT_VOLUME_API_VERSION,
"OS_NETWORK_API_VERSION": DEFAULT_NETWORK_API_VERSION,
}
- self.useFixture(EnvFixture(env.copy()))
-
- def test_shell_args_no_options(self):
- _shell = make_shell()
- with mock.patch("openstackclient.shell.OpenStackShell.initialize_app",
- self.app):
- fake_execute(_shell, "list user")
- self.app.assert_called_with(["list", "user"])
-
- def test_shell_args_ca_options(self):
- _shell = make_shell()
-
- # NOTE(dtroyer): The commented out asserts below are the desired
- # behaviour and will be uncommented when the
- # handling for --verify and --insecure is fixed.
-
- # Default
- fake_execute(_shell, "list user")
- self.assertIsNone(_shell.options.verify)
- self.assertIsNone(_shell.options.insecure)
- self.assertEqual('', _shell.options.cacert)
- self.assertTrue(_shell.verify)
-
- # --verify
- fake_execute(_shell, "--verify list user")
- self.assertTrue(_shell.options.verify)
- self.assertIsNone(_shell.options.insecure)
- self.assertEqual('', _shell.options.cacert)
- self.assertTrue(_shell.verify)
-
- # --insecure
- fake_execute(_shell, "--insecure list user")
- self.assertIsNone(_shell.options.verify)
- self.assertTrue(_shell.options.insecure)
- self.assertEqual('', _shell.options.cacert)
- self.assertFalse(_shell.verify)
-
- # --os-cacert
- fake_execute(_shell, "--os-cacert foo list user")
- self.assertIsNone(_shell.options.verify)
- self.assertIsNone(_shell.options.insecure)
- self.assertEqual('foo', _shell.options.cacert)
- self.assertTrue(_shell.verify)
-
- # --os-cacert and --verify
- fake_execute(_shell, "--os-cacert foo --verify list user")
- self.assertTrue(_shell.options.verify)
- self.assertIsNone(_shell.options.insecure)
- self.assertEqual('foo', _shell.options.cacert)
- self.assertTrue(_shell.verify)
-
- # --os-cacert and --insecure
- # NOTE(dtroyer): Per bug https://bugs.launchpad.net/bugs/1447784
- # in this combination --insecure now overrides any
- # --os-cacert setting, where before --insecure
- # was ignored if --os-cacert was set.
- fake_execute(_shell, "--os-cacert foo --insecure list user")
- self.assertIsNone(_shell.options.verify)
- self.assertTrue(_shell.options.insecure)
- self.assertEqual('foo', _shell.options.cacert)
- self.assertFalse(_shell.verify)
-
- def test_shell_args_cert_options(self):
- _shell = make_shell()
-
- # Default
- fake_execute(_shell, "list user")
- self.assertEqual('', _shell.options.cert)
- self.assertEqual('', _shell.options.key)
-
- # --os-cert
- fake_execute(_shell, "--os-cert mycert list user")
- self.assertEqual('mycert', _shell.options.cert)
- self.assertEqual('', _shell.options.key)
-
- # --os-key
- fake_execute(_shell, "--os-key mickey list user")
- self.assertEqual('', _shell.options.cert)
- self.assertEqual('mickey', _shell.options.key)
+ self.useFixture(osc_lib_test_utils.EnvFixture(env.copy()))
def test_default_env(self):
flag = ""
@@ -605,220 +409,34 @@ class TestShellCli(TestShell):
}
self._assert_cli(flag, kwargs)
- @mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
- def test_shell_args_cloud_no_vendor(self, config_mock):
- config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_1))
- _shell = make_shell()
-
- fake_execute(
- _shell,
- "--os-cloud scc list user",
- )
- self.assertEqual(
- 'scc',
- _shell.cloud.name,
- )
-
- # These come from clouds.yaml
- self.assertEqual(
- DEFAULT_AUTH_URL,
- _shell.cloud.config['auth']['auth_url'],
- )
- self.assertEqual(
- DEFAULT_PROJECT_NAME,
- _shell.cloud.config['auth']['project_name'],
- )
- self.assertEqual(
- 'zaphod',
- _shell.cloud.config['auth']['username'],
- )
- self.assertEqual(
- 'occ-cloud',
- _shell.cloud.config['region_name'],
- )
- self.assertEqual(
- 'glazed',
- _shell.cloud.config['donut'],
- )
- self.assertEqual(
- 'public',
- _shell.cloud.config['interface'],
- )
-
- @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
- @mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
- def test_shell_args_cloud_public(self, config_mock, public_mock):
- config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
- public_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
- _shell = make_shell()
-
- fake_execute(
- _shell,
- "--os-cloud megacloud list user",
- )
- self.assertEqual(
- 'megacloud',
- _shell.cloud.name,
- )
-
- # These come from clouds-public.yaml
- self.assertEqual(
- DEFAULT_AUTH_URL,
- _shell.cloud.config['auth']['auth_url'],
- )
- self.assertEqual(
- 'cake',
- _shell.cloud.config['donut'],
- )
-
- # These come from clouds.yaml
- self.assertEqual(
- 'heart-o-gold',
- _shell.cloud.config['auth']['project_name'],
- )
- self.assertEqual(
- 'zaphod',
- _shell.cloud.config['auth']['username'],
- )
- self.assertEqual(
- 'occ-cloud',
- _shell.cloud.config['region_name'],
- )
-
- self.assertEqual('mycert', _shell.cloud.config['cert'])
- self.assertEqual('mickey', _shell.cloud.config['key'])
-
- @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
- @mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
- def test_shell_args_precedence(self, config_mock, vendor_mock):
- config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
- vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
- _shell = make_shell()
-
- # Test command option overriding config file value
- fake_execute(
- _shell,
- "--os-cloud megacloud --os-region-name krikkit list user",
- )
- self.assertEqual(
- 'megacloud',
- _shell.cloud.name,
- )
-
- # These come from clouds-public.yaml
- self.assertEqual(
- DEFAULT_AUTH_URL,
- _shell.cloud.config['auth']['auth_url'],
- )
- self.assertEqual(
- 'cake',
- _shell.cloud.config['donut'],
- )
-
- # These come from clouds.yaml
- self.assertEqual(
- 'heart-o-gold',
- _shell.cloud.config['auth']['project_name'],
- )
- self.assertEqual(
- 'zaphod',
- _shell.cloud.config['auth']['username'],
- )
- self.assertEqual(
- 'krikkit',
- _shell.cloud.config['region_name'],
- )
-
-
-class TestShellCliEnv(TestShell):
+
+class TestShellArgV(TestShell):
+ """Test the deferred help flag"""
def setUp(self):
- super(TestShellCliEnv, self).setUp()
- env = {
- 'OS_REGION_NAME': 'occ-env',
- }
- self.useFixture(EnvFixture(env.copy()))
-
- @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
- @mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
- def test_shell_args_precedence_1(self, config_mock, vendor_mock):
- config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
- vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
- _shell = make_shell()
-
- # Test env var
- fake_execute(
- _shell,
- "--os-cloud megacloud list user",
- )
- self.assertEqual(
- 'megacloud',
- _shell.cloud.name,
- )
-
- # These come from clouds-public.yaml
- self.assertEqual(
- DEFAULT_AUTH_URL,
- _shell.cloud.config['auth']['auth_url'],
- )
- self.assertEqual(
- 'cake',
- _shell.cloud.config['donut'],
- )
-
- # These come from clouds.yaml
- self.assertEqual(
- 'heart-o-gold',
- _shell.cloud.config['auth']['project_name'],
- )
- self.assertEqual(
- 'zaphod',
- _shell.cloud.config['auth']['username'],
- )
- self.assertEqual(
- 'occ-env',
- _shell.cloud.config['region_name'],
- )
-
- @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
- @mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
- def test_shell_args_precedence_2(self, config_mock, vendor_mock):
- config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
- vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
- _shell = make_shell()
-
- # Test command option overriding config file value
- fake_execute(
- _shell,
- "--os-cloud megacloud --os-region-name krikkit list user",
- )
- self.assertEqual(
- 'megacloud',
- _shell.cloud.name,
- )
-
- # These come from clouds-public.yaml
- self.assertEqual(
- DEFAULT_AUTH_URL,
- _shell.cloud.config['auth']['auth_url'],
- )
- self.assertEqual(
- 'cake',
- _shell.cloud.config['donut'],
- )
-
- # These come from clouds.yaml
- self.assertEqual(
- 'heart-o-gold',
- _shell.cloud.config['auth']['project_name'],
- )
- self.assertEqual(
- 'zaphod',
- _shell.cloud.config['auth']['username'],
- )
-
- # These come from the command line
- self.assertEqual(
- 'krikkit',
- _shell.cloud.config['region_name'],
- )
+ super(TestShellArgV, self).setUp()
+
+ def test_shell_argv(self):
+ """Test argv decoding
+
+ Python 2 does nothing with argv while Python 3 decodes it into
+ Unicode before we ever see it. We manually decode when running
+ under Python 2 so verify that we get the right argv types.
+
+ Use the argv supplied by the test runner so we get actual Python
+ runtime behaviour; we only need to check the type of argv[0]
+ which will alwyas be present.
+ """
+
+ with mock.patch(
+ self.shell_class_name + ".run",
+ self.app,
+ ):
+ # Ensure type gets through unmolested through shell.main()
+ argv = sys.argv
+ shell.main(sys.argv)
+ self.assertEqual(type(argv[0]), type(self.app.call_args[0][0][0]))
+
+ # When shell.main() gets sys.argv itself it should be decoded
+ shell.main()
+ self.assertEqual(type(u'x'), type(self.app.call_args[0][0][0]))
diff --git a/openstackclient/tests/test_shell_integ.py b/openstackclient/tests/test_shell_integ.py
new file mode 100644
index 0000000..d50113b
--- /dev/null
+++ b/openstackclient/tests/test_shell_integ.py
@@ -0,0 +1,668 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+import mock
+
+from keystoneauth1 import fixture as ksa_fixture
+from osc_lib.tests import utils as osc_lib_utils
+from requests_mock.contrib import fixture
+
+from openstackclient import shell
+from openstackclient.tests import test_shell
+from openstackclient.tests import utils
+
+HOST = "192.168.5.41"
+URL_BASE = "http://%s/identity" % HOST
+
+V2_AUTH_URL = URL_BASE + "/v2.0/"
+V2_VERSION_RESP = {
+ "version": {
+ "status": "stable",
+ "updated": "2014-04-17T00:00:00Z",
+ "media-types": [
+ {
+ "base": "application/json",
+ "type": "application/vnd.openstack.identity-v2.0+json",
+ },
+ ],
+ "id": "v2.0",
+ "links": [
+ {
+ "href": V2_AUTH_URL,
+ "rel": "self",
+ },
+ {
+ "href": "http://docs.openstack.org/",
+ "type": "text/html",
+ "rel": "describedby",
+ },
+ ],
+ },
+}
+
+V3_AUTH_URL = URL_BASE + "/v3/"
+V3_VERSION_RESP = {
+ "version": {
+ "status": "stable",
+ "updated": "2016-04-04T00:00:00Z",
+ "media-types": [{
+ "base": "application/json",
+ "type": "application/vnd.openstack.identity-v3+json",
+ }],
+ "id": "v3.6",
+ "links": [{
+ "href": V3_AUTH_URL,
+ "rel": "self",
+ }]
+ }
+}
+
+
+class TestShellInteg(utils.TestCase):
+
+ def setUp(self):
+ super(TestShellInteg, self).setUp()
+
+ self.requests_mock = self.useFixture(fixture.Fixture())
+
+
+class TestShellCliV2Integ(TestShellInteg):
+
+ def setUp(self):
+ super(TestShellCliV2Integ, self).setUp()
+ env = {
+ "OS_AUTH_URL": V2_AUTH_URL,
+ "OS_PROJECT_NAME": test_shell.DEFAULT_PROJECT_NAME,
+ "OS_USERNAME": test_shell.DEFAULT_USERNAME,
+ "OS_PASSWORD": test_shell.DEFAULT_PASSWORD,
+ "OS_IDENTITY_API_VERSION": "2",
+ }
+ self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env)))
+
+ self.token = ksa_fixture.V2Token(
+ tenant_name=test_shell.DEFAULT_PROJECT_NAME,
+ user_name=test_shell.DEFAULT_USERNAME,
+ )
+
+ # Set up the v2 auth routes
+ self.requests_mock.register_uri(
+ 'GET',
+ V2_AUTH_URL,
+ json=V2_VERSION_RESP,
+ status_code=200,
+ )
+ self.requests_mock.register_uri(
+ 'POST',
+ V2_AUTH_URL + 'tokens',
+ json=self.token,
+ status_code=200,
+ )
+
+ def test_shell_args_no_options(self):
+ _shell = shell.OpenStackShell()
+ _shell.run("configuration show".split())
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check discovery request
+ self.assertEqual(
+ V2_AUTH_URL,
+ self.requests_mock.request_history[0].url,
+ )
+
+ # Check auth request
+ auth_req = self.requests_mock.request_history[1].json()
+
+ self.assertEqual(
+ test_shell.DEFAULT_PROJECT_NAME,
+ auth_req['auth']['tenantName'],
+ )
+ self.assertEqual(
+ test_shell.DEFAULT_USERNAME,
+ auth_req['auth']['passwordCredentials']['username'],
+ )
+ self.assertEqual(
+ test_shell.DEFAULT_PASSWORD,
+ auth_req['auth']['passwordCredentials']['password'],
+ )
+
+ def test_shell_args_verify(self):
+ _shell = shell.OpenStackShell()
+ _shell.run("--verify configuration show".split())
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check verify
+ self.assertTrue(self.requests_mock.request_history[0].verify)
+
+ def test_shell_args_insecure(self):
+ _shell = shell.OpenStackShell()
+ _shell.run("--insecure configuration show".split())
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check verify
+ self.assertFalse(self.requests_mock.request_history[0].verify)
+
+ def test_shell_args_cacert(self):
+ _shell = shell.OpenStackShell()
+ _shell.run("--os-cacert xyzpdq configuration show".split())
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check verify
+ self.assertEqual(
+ 'xyzpdq',
+ self.requests_mock.request_history[0].verify,
+ )
+
+ def test_shell_args_cacert_insecure(self):
+ _shell = shell.OpenStackShell()
+ _shell.run("--os-cacert xyzpdq --insecure configuration show".split())
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check verify
+ self.assertFalse(self.requests_mock.request_history[0].verify)
+
+
+class TestShellCliV2IgnoreInteg(TestShellInteg):
+
+ def setUp(self):
+ super(TestShellCliV2IgnoreInteg, self).setUp()
+ env = {
+ "OS_AUTH_URL": V2_AUTH_URL,
+ "OS_PROJECT_NAME": test_shell.DEFAULT_PROJECT_NAME,
+ "OS_USERNAME": test_shell.DEFAULT_USERNAME,
+ "OS_PASSWORD": test_shell.DEFAULT_PASSWORD,
+ "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID,
+ "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID,
+ "OS_IDENTITY_API_VERSION": "2",
+ }
+ self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env)))
+
+ self.token = ksa_fixture.V2Token(
+ tenant_name=test_shell.DEFAULT_PROJECT_NAME,
+ user_name=test_shell.DEFAULT_USERNAME,
+ )
+
+ # Set up the v2 auth routes
+ self.requests_mock.register_uri(
+ 'GET',
+ V2_AUTH_URL,
+ json=V2_VERSION_RESP,
+ status_code=200,
+ )
+ self.requests_mock.register_uri(
+ 'POST',
+ V2_AUTH_URL + 'tokens',
+ json=self.token,
+ status_code=200,
+ )
+
+ def test_shell_args_ignore_v3(self):
+ _shell = shell.OpenStackShell()
+ _shell.run("configuration show".split())
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check discovery request
+ self.assertEqual(
+ V2_AUTH_URL,
+ self.requests_mock.request_history[0].url,
+ )
+
+ # Check auth request
+ auth_req = self.requests_mock.request_history[1].json()
+
+ self.assertEqual(
+ test_shell.DEFAULT_PROJECT_NAME,
+ auth_req['auth']['tenantName'],
+ )
+ self.assertEqual(
+ test_shell.DEFAULT_USERNAME,
+ auth_req['auth']['passwordCredentials']['username'],
+ )
+ self.assertEqual(
+ test_shell.DEFAULT_PASSWORD,
+ auth_req['auth']['passwordCredentials']['password'],
+ )
+
+
+class TestShellCliV3Integ(TestShellInteg):
+
+ def setUp(self):
+ super(TestShellCliV3Integ, self).setUp()
+ env = {
+ "OS_AUTH_URL": V3_AUTH_URL,
+ "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID,
+ "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID,
+ "OS_USERNAME": test_shell.DEFAULT_USERNAME,
+ "OS_PASSWORD": test_shell.DEFAULT_PASSWORD,
+ "OS_IDENTITY_API_VERSION": "3",
+ }
+ self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env)))
+
+ self.token = ksa_fixture.V3Token(
+ project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID,
+ user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID,
+ user_name=test_shell.DEFAULT_USERNAME,
+ )
+
+ # Set up the v3 auth routes
+ self.requests_mock.register_uri(
+ 'GET',
+ V3_AUTH_URL,
+ json=V3_VERSION_RESP,
+ status_code=200,
+ )
+ self.requests_mock.register_uri(
+ 'POST',
+ V3_AUTH_URL + 'auth/tokens',
+ json=self.token,
+ status_code=200,
+ )
+
+ def test_shell_args_no_options(self):
+ _shell = shell.OpenStackShell()
+ _shell.run("configuration show".split())
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check discovery request
+ self.assertEqual(
+ V3_AUTH_URL,
+ self.requests_mock.request_history[0].url,
+ )
+
+ # Check auth request
+ auth_req = self.requests_mock.request_history[1].json()
+
+ self.assertEqual(
+ test_shell.DEFAULT_PROJECT_DOMAIN_ID,
+ auth_req['auth']['identity']['password']['user']['domain']['id'],
+ )
+ self.assertEqual(
+ test_shell.DEFAULT_USERNAME,
+ auth_req['auth']['identity']['password']['user']['name'],
+ )
+ self.assertEqual(
+ test_shell.DEFAULT_PASSWORD,
+ auth_req['auth']['identity']['password']['user']['password'],
+ )
+
+ def test_shell_args_verify(self):
+ _shell = shell.OpenStackShell()
+ _shell.run("--verify configuration show".split())
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check verify
+ self.assertTrue(self.requests_mock.request_history[0].verify)
+
+ def test_shell_args_insecure(self):
+ _shell = shell.OpenStackShell()
+ _shell.run("--insecure configuration show".split())
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check verify
+ self.assertFalse(self.requests_mock.request_history[0].verify)
+
+ def test_shell_args_cacert(self):
+ _shell = shell.OpenStackShell()
+ _shell.run("--os-cacert xyzpdq configuration show".split())
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check verify
+ self.assertEqual(
+ 'xyzpdq',
+ self.requests_mock.request_history[0].verify,
+ )
+
+ def test_shell_args_cacert_insecure(self):
+ # This test verifies the outcome of bug 1447784
+ # https://bugs.launchpad.net/python-openstackclient/+bug/1447784
+ _shell = shell.OpenStackShell()
+ _shell.run("--os-cacert xyzpdq --insecure configuration show".split())
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check verify
+ self.assertFalse(self.requests_mock.request_history[0].verify)
+
+
+class TestShellCliV3Prompt(TestShellInteg):
+
+ def setUp(self):
+ super(TestShellCliV3Prompt, self).setUp()
+ env = {
+ "OS_AUTH_URL": V3_AUTH_URL,
+ "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID,
+ "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID,
+ "OS_USERNAME": test_shell.DEFAULT_USERNAME,
+ "OS_IDENTITY_API_VERSION": "3",
+ }
+ self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env)))
+
+ self.token = ksa_fixture.V3Token(
+ project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID,
+ user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID,
+ user_name=test_shell.DEFAULT_USERNAME,
+ )
+
+ # Set up the v3 auth routes
+ self.requests_mock.register_uri(
+ 'GET',
+ V3_AUTH_URL,
+ json=V3_VERSION_RESP,
+ status_code=200,
+ )
+ self.requests_mock.register_uri(
+ 'POST',
+ V3_AUTH_URL + 'auth/tokens',
+ json=self.token,
+ status_code=200,
+ )
+
+ @mock.patch("osc_lib.shell.prompt_for_password")
+ def test_shell_callback(self, mock_prompt):
+ mock_prompt.return_value = "qaz"
+ _shell = shell.OpenStackShell()
+ _shell.run("configuration show".split())
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check password callback set correctly
+ self.assertEqual(
+ mock_prompt,
+ _shell.cloud._openstack_config._pw_callback
+ )
+
+ # Check auth request
+ auth_req = self.requests_mock.request_history[1].json()
+
+ # Check returned password from prompt function
+ self.assertEqual(
+ "qaz",
+ auth_req['auth']['identity']['password']['user']['password'],
+ )
+
+
+class TestShellCliPrecedence(TestShellInteg):
+ """Validate option precedence rules without clouds.yaml
+
+ Global option values may be set in three places:
+ * command line options
+ * environment variables
+ * clouds.yaml
+
+ Verify that the above order is the precedence used,
+ i.e. a command line option overrides all others, etc
+ """
+
+ def setUp(self):
+ super(TestShellCliPrecedence, self).setUp()
+ env = {
+ "OS_AUTH_URL": V3_AUTH_URL,
+ "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID,
+ "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID,
+ "OS_USERNAME": test_shell.DEFAULT_USERNAME,
+ "OS_IDENTITY_API_VERSION": "3",
+ }
+ self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env)))
+
+ self.token = ksa_fixture.V3Token(
+ project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID,
+ user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID,
+ user_name=test_shell.DEFAULT_USERNAME,
+ )
+
+ # Set up the v3 auth routes
+ self.requests_mock.register_uri(
+ 'GET',
+ V3_AUTH_URL,
+ json=V3_VERSION_RESP,
+ status_code=200,
+ )
+ self.requests_mock.register_uri(
+ 'POST',
+ V3_AUTH_URL + 'auth/tokens',
+ json=self.token,
+ status_code=200,
+ )
+
+ # Patch a v3 auth URL into the o-c-c data
+ test_shell.PUBLIC_1['public-clouds']['megadodo']['auth']['auth_url'] \
+ = V3_AUTH_URL
+
+ def test_shell_args_options(self):
+ """Verify command line options override environment variables"""
+
+ _shell = shell.OpenStackShell()
+ _shell.run(
+ "--os-username zarquon --os-password qaz "
+ "configuration show".split(),
+ )
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check discovery request
+ self.assertEqual(
+ V3_AUTH_URL,
+ self.requests_mock.request_history[0].url,
+ )
+
+ # Check auth request
+ auth_req = self.requests_mock.request_history[1].json()
+
+ # -env, -cli
+ # No test, everything not specified tests this
+
+ # -env, +cli
+ self.assertEqual(
+ 'qaz',
+ auth_req['auth']['identity']['password']['user']['password'],
+ )
+
+ # +env, -cli
+ self.assertEqual(
+ test_shell.DEFAULT_PROJECT_DOMAIN_ID,
+ auth_req['auth']['identity']['password']['user']['domain']['id'],
+ )
+
+ # +env, +cli
+ self.assertEqual(
+ 'zarquon',
+ auth_req['auth']['identity']['password']['user']['name'],
+ )
+
+
+class TestShellCliPrecedenceOCC(TestShellInteg):
+ """Validate option precedence rules with clouds.yaml
+
+ Global option values may be set in three places:
+ * command line options
+ * environment variables
+ * clouds.yaml
+
+ Verify that the above order is the precedence used,
+ i.e. a command line option overrides all others, etc
+ """
+
+ def setUp(self):
+ super(TestShellCliPrecedenceOCC, self).setUp()
+ env = {
+ "OS_CLOUD": "megacloud",
+ "OS_AUTH_URL": V3_AUTH_URL,
+ "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID,
+ "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID,
+ "OS_USERNAME": test_shell.DEFAULT_USERNAME,
+ "OS_IDENTITY_API_VERSION": "3",
+ "OS_CLOUD_NAME": "qaz",
+ }
+ self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env)))
+
+ self.token = ksa_fixture.V3Token(
+ project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID,
+ user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID,
+ user_name=test_shell.DEFAULT_USERNAME,
+ )
+
+ # Set up the v3 auth routes
+ self.requests_mock.register_uri(
+ 'GET',
+ V3_AUTH_URL,
+ json=V3_VERSION_RESP,
+ status_code=200,
+ )
+ self.requests_mock.register_uri(
+ 'POST',
+ V3_AUTH_URL + 'auth/tokens',
+ json=self.token,
+ status_code=200,
+ )
+
+ # Patch a v3 auth URL into the o-c-c data
+ test_shell.PUBLIC_1['public-clouds']['megadodo']['auth']['auth_url'] \
+ = V3_AUTH_URL
+
+ @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
+ @mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
+ def test_shell_args_precedence_1(self, config_mock, vendor_mock):
+ """Precedence run 1
+
+ Run 1 has --os-password on CLI
+ """
+
+ config_mock.return_value = (
+ 'file.yaml',
+ copy.deepcopy(test_shell.CLOUD_2),
+ )
+ vendor_mock.return_value = (
+ 'file.yaml',
+ copy.deepcopy(test_shell.PUBLIC_1),
+ )
+ _shell = shell.OpenStackShell()
+ _shell.run(
+ "--os-password qaz configuration show".split(),
+ )
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check discovery request
+ self.assertEqual(
+ V3_AUTH_URL,
+ self.requests_mock.request_history[0].url,
+ )
+
+ # Check auth request
+ auth_req = self.requests_mock.request_history[1].json()
+
+ # -env, -cli, -occ
+ # No test, everything not specified tests this
+
+ # -env, -cli, +occ
+ self.assertEqual(
+ "heart-o-gold",
+ auth_req['auth']['scope']['project']['name'],
+ )
+
+ # -env, +cli, -occ
+ self.assertEqual(
+ 'qaz',
+ auth_req['auth']['identity']['password']['user']['password'],
+ )
+
+ # -env, +cli, +occ
+
+ # +env, -cli, -occ
+ self.assertEqual(
+ test_shell.DEFAULT_USER_DOMAIN_ID,
+ auth_req['auth']['identity']['password']['user']['domain']['id'],
+ )
+
+ # +env, -cli, +occ
+ print("auth_req: %s" % auth_req['auth'])
+ self.assertEqual(
+ test_shell.DEFAULT_USERNAME,
+ auth_req['auth']['identity']['password']['user']['name'],
+ )
+
+ # +env, +cli, -occ
+ # see test_shell_args_precedence_2()
+
+ # +env, +cli, +occ
+ # see test_shell_args_precedence_2()
+
+ @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
+ @mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
+ def test_shell_args_precedence_2(self, config_mock, vendor_mock):
+ """Precedence run 2
+
+ Run 2 has --os-username, --os-password, --os-project-domain-id on CLI
+ """
+
+ config_mock.return_value = (
+ 'file.yaml',
+ copy.deepcopy(test_shell.CLOUD_2),
+ )
+ vendor_mock.return_value = (
+ 'file.yaml',
+ copy.deepcopy(test_shell.PUBLIC_1),
+ )
+ _shell = shell.OpenStackShell()
+ _shell.run(
+ "--os-username zarquon --os-password qaz "
+ "--os-project-domain-id 5678 configuration show".split(),
+ )
+
+ # Check general calls
+ self.assertEqual(len(self.requests_mock.request_history), 2)
+
+ # Check discovery request
+ self.assertEqual(
+ V3_AUTH_URL,
+ self.requests_mock.request_history[0].url,
+ )
+
+ # Check auth request
+ auth_req = self.requests_mock.request_history[1].json()
+
+ # +env, +cli, -occ
+ self.assertEqual(
+ '5678',
+ auth_req['auth']['scope']['project']['domain']['id'],
+ )
+
+ # +env, +cli, +occ
+ print("auth_req: %s" % auth_req['auth'])
+ self.assertEqual(
+ 'zarquon',
+ auth_req['auth']['identity']['password']['user']['name'],
+ )
diff --git a/openstackclient/tests/utils.py b/openstackclient/tests/utils.py
index 8dead71..f3ab5ea 100644
--- a/openstackclient/tests/utils.py
+++ b/openstackclient/tests/utils.py
@@ -14,9 +14,8 @@
# under the License.
#
-import os
-
import fixtures
+import os
import testtools
from openstackclient.tests import fakes
diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/volume/test_find_resource.py
index 00cc46a..982b02f 100644
--- a/openstackclient/tests/volume/test_find_resource.py
+++ b/openstackclient/tests/volume/test_find_resource.py
@@ -17,9 +17,9 @@ import mock
from cinderclient.v1 import volume_snapshots
from cinderclient.v1 import volumes
+from osc_lib import exceptions
+from osc_lib import utils
-from openstackclient.common import exceptions
-from openstackclient.common import utils
from openstackclient.tests import utils as test_utils
from openstackclient.volume import client # noqa
@@ -44,7 +44,9 @@ class TestFindResourceVolumes(test_utils.TestCase):
api.client.get = mock.Mock()
resp = mock.Mock()
body = {"volumes": [{"id": ID, 'display_name': NAME}]}
- api.client.get.side_effect = [Exception("Not found"), (resp, body)]
+ api.client.get.side_effect = [Exception("Not found"),
+ Exception("Not found"),
+ (resp, body)]
self.manager = volumes.VolumeManager(api)
def test_find(self):
@@ -66,7 +68,9 @@ class TestFindResourceVolumeSnapshots(test_utils.TestCase):
api.client.get = mock.Mock()
resp = mock.Mock()
body = {"snapshots": [{"id": ID, 'display_name': NAME}]}
- api.client.get.side_effect = [Exception("Not found"), (resp, body)]
+ api.client.get.side_effect = [Exception("Not found"),
+ Exception("Not found"),
+ (resp, body)]
self.manager = volume_snapshots.SnapshotManager(api)
def test_find(self):
diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/volume/v1/fakes.py
index 6c34986..2584d4b 100644
--- a/openstackclient/tests/volume/v1/fakes.py
+++ b/openstackclient/tests/volume/v1/fakes.py
@@ -157,7 +157,7 @@ class FakeTransfer(object):
:param Dictionary attrs:
A dictionary with all attributes of Transfer Request
- :retrun:
+ :return:
A FakeResource object with volume_id, name, id.
"""
# Set default attribute
@@ -207,7 +207,7 @@ class FakeService(object):
:param Dictionary attrs:
A dictionary with all attributes of service
- :retrun:
+ :return:
A FakeResource object with host, status, etc.
"""
# Set default attribute
diff --git a/openstackclient/tests/volume/v1/test_qos_specs.py b/openstackclient/tests/volume/v1/test_qos_specs.py
index 4943f5d..4e1733f 100644
--- a/openstackclient/tests/volume/v1/test_qos_specs.py
+++ b/openstackclient/tests/volume/v1/test_qos_specs.py
@@ -15,7 +15,8 @@
import copy
-from openstackclient.common import utils
+from osc_lib import utils
+
from openstackclient.tests import fakes
from openstackclient.tests.volume.v1 import fakes as volume_fakes
from openstackclient.volume.v1 import qos_specs
@@ -210,7 +211,7 @@ class TestQosDelete(TestQos):
result = self.cmd.take_action(parsed_args)
- self.qos_mock.delete.assert_called_with(volume_fakes.qos_id)
+ self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, False)
self.assertIsNone(result)
def test_qos_delete_with_name(self):
@@ -224,7 +225,23 @@ class TestQosDelete(TestQos):
result = self.cmd.take_action(parsed_args)
- self.qos_mock.delete.assert_called_with(volume_fakes.qos_id)
+ self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, False)
+ self.assertIsNone(result)
+
+ def test_qos_delete_with_force(self):
+ arglist = [
+ '--force',
+ volume_fakes.qos_id
+ ]
+ verifylist = [
+ ('force', True),
+ ('qos_specs', [volume_fakes.qos_id])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, True)
self.assertIsNone(result)
diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py
index e4f51bb..e41c2a7 100644
--- a/openstackclient/tests/volume/v1/test_volume.py
+++ b/openstackclient/tests/volume/v1/test_volume.py
@@ -14,6 +14,7 @@
#
import copy
+import mock
from openstackclient.tests import fakes
from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
@@ -49,6 +50,9 @@ class TestVolume(volume_fakes.TestVolumev1):
class TestVolumeCreate(TestVolume):
+ project = identity_fakes.FakeProject.create_one_project()
+ user = identity_fakes.FakeUser.create_one_user()
+
columns = (
'attach_status',
'availability_zone',
@@ -167,28 +171,20 @@ class TestVolumeCreate(TestVolume):
def test_volume_create_user_project_id(self):
# Return a project
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
# Return a user
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.users_mock.get.return_value = self.user
arglist = [
'--size', str(volume_fakes.volume_size),
- '--project', identity_fakes.project_id,
- '--user', identity_fakes.user_id,
+ '--project', self.project.id,
+ '--user', self.user.id,
volume_fakes.volume_name,
]
verifylist = [
('size', volume_fakes.volume_size),
- ('project', identity_fakes.project_id),
- ('user', identity_fakes.user_id),
+ ('project', self.project.id),
+ ('user', self.user.id),
('name', volume_fakes.volume_name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -210,8 +206,8 @@ class TestVolumeCreate(TestVolume):
volume_fakes.volume_name,
None,
None,
- identity_fakes.user_id,
- identity_fakes.project_id,
+ self.user.id,
+ self.project.id,
None,
None,
None,
@@ -222,28 +218,20 @@ class TestVolumeCreate(TestVolume):
def test_volume_create_user_project_name(self):
# Return a project
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
# Return a user
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.users_mock.get.return_value = self.user
arglist = [
'--size', str(volume_fakes.volume_size),
- '--project', identity_fakes.project_name,
- '--user', identity_fakes.user_name,
+ '--project', self.project.name,
+ '--user', self.user.name,
volume_fakes.volume_name,
]
verifylist = [
('size', volume_fakes.volume_size),
- ('project', identity_fakes.project_name),
- ('user', identity_fakes.user_name),
+ ('project', self.project.name),
+ ('user', self.user.name),
('name', volume_fakes.volume_name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -265,8 +253,8 @@ class TestVolumeCreate(TestVolume):
volume_fakes.volume_name,
None,
None,
- identity_fakes.user_id,
- identity_fakes.project_id,
+ self.user.id,
+ self.project.id,
None,
None,
None,
@@ -656,7 +644,8 @@ class TestVolumeSet(TestVolume):
)
self.assertIsNone(result)
- def test_volume_set_size_smaller(self):
+ @mock.patch.object(volume.LOG, 'error')
+ def test_volume_set_size_smaller(self, mock_log_error):
arglist = [
'--size', '100',
volume_fakes.volume_name,
@@ -672,12 +661,13 @@ class TestVolumeSet(TestVolume):
result = self.cmd.take_action(parsed_args)
- self.assertEqual("New size must be greater than %s GB" %
- volume_fakes.volume_size,
- self.app.log.messages.get('error'))
+ mock_log_error.assert_called_with("New size must be greater "
+ "than %s GB",
+ volume_fakes.volume_size)
self.assertIsNone(result)
- def test_volume_set_size_not_available(self):
+ @mock.patch.object(volume.LOG, 'error')
+ def test_volume_set_size_not_available(self, mock_log_error):
self.volumes_mock.get.return_value.status = 'error'
arglist = [
'--size', '130',
@@ -694,9 +684,9 @@ class TestVolumeSet(TestVolume):
result = self.cmd.take_action(parsed_args)
- self.assertEqual("Volume is in %s state, it must be available before "
- "size can be extended" % 'error',
- self.app.log.messages.get('error'))
+ mock_log_error.assert_called_with("Volume is in %s state, it must be "
+ "available before size can be "
+ "extended", 'error')
self.assertIsNone(result)
def test_volume_set_property(self):
diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py
index e61fe8a..6809beb 100644
--- a/openstackclient/tests/volume/v2/fakes.py
+++ b/openstackclient/tests/volume/v2/fakes.py
@@ -17,220 +17,13 @@ import mock
import random
import uuid
-from openstackclient.common import utils as common_utils
+from osc_lib import utils as common_utils
+
from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
from openstackclient.tests import utils
-volume_attachment_server = {
- 'device': '/dev/ice',
- 'server_id': '1233',
-}
-
-volume_id = "ce26708d-a7f8-4b4b-9861-4a80256615a6"
-volume_name = "fake_volume"
-volume_description = "fake description"
-volume_status = "available"
-volume_size = 20
-volume_type = "fake_lvmdriver-1"
-volume_metadata = {
- 'Alpha': 'a',
- 'Beta': 'b',
- 'Gamma': 'g',
-}
-volume_metadata_str = "Alpha='a', Beta='b', Gamma='g'"
-volume_snapshot_id = 1
-volume_availability_zone = "nova"
-volume_attachments = [volume_attachment_server]
-
-VOLUME = {
- "id": volume_id,
- "name": volume_name,
- "description": volume_description,
- "status": volume_status,
- "size": volume_size,
- "volume_type": volume_type,
- "metadata": volume_metadata,
- "snapshot_id": volume_snapshot_id,
- "availability_zone": volume_availability_zone,
- "attachments": volume_attachments
-}
-
-VOLUME_columns = (
- "attachments",
- "availability_zone",
- "description",
- "id",
- "name",
- "properties",
- "size",
- "snapshot_id",
- "status",
- "type"
-)
-
-VOLUME_data = (
- volume_attachments,
- volume_availability_zone,
- volume_description,
- volume_id,
- volume_name,
- common_utils.format_dict(volume_metadata),
- volume_size,
- volume_snapshot_id,
- volume_status,
- volume_type
-)
-
-
-snapshot_id = "cb2d364e-4d1c-451a-8c68-b5bbcb340fb2"
-snapshot_name = "fake_snapshot"
-snapshot_description = "fake description"
-snapshot_size = 10
-snapshot_metadata = {
- "foo": "bar"
-}
-snapshot_volume_id = "bdbae8dc-e6ca-43c0-8076-951cc1b093a4"
-
-SNAPSHOT = {
- "id": snapshot_id,
- "name": snapshot_name,
- "description": snapshot_description,
- "size": snapshot_size,
- "status": "available",
- "metadata": snapshot_metadata,
- "created_at": "2015-06-03T18:49:19.000000",
- "volume_id": volume_name
-}
-EXPECTED_SNAPSHOT = copy.deepcopy(SNAPSHOT)
-EXPECTED_SNAPSHOT.pop("metadata")
-EXPECTED_SNAPSHOT['properties'] = "foo='bar'"
-SNAPSHOT_columns = tuple(sorted(EXPECTED_SNAPSHOT))
-SNAPSHOT_data = tuple((EXPECTED_SNAPSHOT[x]
- for x in sorted(EXPECTED_SNAPSHOT)))
-
-
-type_id = "5520dc9e-6f9b-4378-a719-729911c0f407"
-type_description = "fake description"
-type_name = "fake-lvmdriver-1"
-type_extra_specs = {
- "foo": "bar"
-}
-
-TYPE = {
- 'id': type_id,
- 'name': type_name,
- 'description': type_description,
- 'extra_specs': type_extra_specs
-}
-
-TYPE_columns = tuple(sorted(TYPE))
-TYPE_data = tuple((TYPE[x] for x in sorted(TYPE)))
-
-formatted_type_properties = "foo='bar'"
-TYPE_FORMATTED = {
- 'id': type_id,
- 'name': type_name,
- 'description': type_description,
- 'properties': formatted_type_properties
-}
-TYPE_FORMATTED_columns = tuple(sorted(TYPE_FORMATTED))
-TYPE_FORMATTED_data = tuple((TYPE_FORMATTED[x] for x in
- sorted(TYPE_FORMATTED)))
-
-backup_id = "3c409fe6-4d03-4a06-aeab-18bdcdf3c8f4"
-backup_volume_id = "bdbae8dc-e6ca-43c0-8076-951cc1b093a4"
-backup_name = "fake_backup"
-backup_description = "fake description"
-backup_object_count = None
-backup_container = None
-backup_size = 10
-backup_status = "error"
-
-BACKUP = {
- "id": backup_id,
- "name": backup_name,
- "volume_id": backup_volume_id,
- "description": backup_description,
- "object_count": backup_object_count,
- "container": backup_container,
- "size": backup_size,
- "status": backup_status,
- "availability_zone": volume_availability_zone,
-}
-
-BACKUP_columns = tuple(sorted(BACKUP))
-BACKUP_data = tuple((BACKUP[x] for x in sorted(BACKUP)))
-
-qos_id = '6f2be1de-997b-4230-b76c-a3633b59e8fb'
-qos_consumer = 'front-end'
-qos_default_consumer = 'both'
-qos_name = "fake-qos-specs"
-qos_specs = {
- 'foo': 'bar',
- 'iops': '9001'
-}
-qos_association = {
- 'association_type': 'volume_type',
- 'name': type_name,
- 'id': type_id
-}
-
-QOS = {
- 'id': qos_id,
- 'consumer': qos_consumer,
- 'name': qos_name
-}
-
-QOS_DEFAULT_CONSUMER = {
- 'id': qos_id,
- 'consumer': qos_default_consumer,
- 'name': qos_name
-}
-
-QOS_WITH_SPECS = {
- 'id': qos_id,
- 'consumer': qos_consumer,
- 'name': qos_name,
- 'specs': qos_specs
-}
-
-QOS_WITH_ASSOCIATIONS = {
- 'id': qos_id,
- 'consumer': qos_consumer,
- 'name': qos_name,
- 'specs': qos_specs,
- 'associations': [qos_association]
-}
-
-image_id = 'im1'
-image_name = 'graven'
-IMAGE = {
- 'id': image_id,
- 'name': image_name
-}
-
-extension_name = 'SchedulerHints'
-extension_namespace = 'http://docs.openstack.org/'\
- 'block-service/ext/scheduler-hints/api/v2'
-extension_description = 'Pass arbitrary key/value'\
- 'pairs to the scheduler.'
-extension_updated = '2013-04-18T00:00:00+00:00'
-extension_alias = 'OS-SCH-HNT'
-extension_links = '[{"href":'\
- '"https://github.com/openstack/block-api", "type":'\
- ' "text/html", "rel": "describedby"}]'
-
-EXTENSION = {
- 'name': extension_name,
- 'namespace': extension_namespace,
- 'description': extension_description,
- 'updated': extension_updated,
- 'alias': extension_alias,
- 'links': extension_links,
-}
-
class FakeTransferClient(object):
@@ -260,7 +53,7 @@ class FakeTransfer(object):
:param Dictionary attrs:
A dictionary with all attributes of Transfer Request
- :retrun:
+ :return:
A FakeResource object with volume_id, name, id.
"""
# Set default attribute
@@ -283,6 +76,38 @@ class FakeTransfer(object):
return transfer
+class FakeTypeAccess(object):
+ """Fake one or more volume type access."""
+
+ @staticmethod
+ def create_one_type_access(attrs=None):
+ """Create a fake volume type access for project.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with Volume_type_ID and Project_ID.
+ """
+ if attrs is None:
+ attrs = {}
+
+ # Set default attributes.
+ type_access_attrs = {
+ 'volume_type_id': 'volume-type-id-' + uuid.uuid4().hex,
+ 'project_id': 'project-id-' + uuid.uuid4().hex,
+ }
+
+ # Overwrite default attributes.
+ type_access_attrs.update(attrs)
+
+ type_access = fakes.FakeResource(
+ None,
+ type_access_attrs,
+ loaded=True)
+
+ return type_access
+
+
class FakeServiceClient(object):
def __init__(self, **kwargs):
@@ -310,7 +135,7 @@ class FakeService(object):
:param Dictionary attrs:
A dictionary with all attributes of service
- :retrun:
+ :return:
A FakeResource object with host, status, etc.
"""
# Set default attribute
@@ -353,32 +178,14 @@ class FakeService(object):
return services
- @staticmethod
- def get_services(services=None, count=2):
- """Get an iterable MagicMock object with a list of faked services.
-
- If services list is provided, then initialize the Mock object with the
- list. Otherwise create one.
-
- :param List services:
- A list of FakeResource objects faking services
- :param Integer count:
- The number of services to be faked
- :return
- An iterable Mock object with side_effect set to a list of faked
- services
- """
- if services is None:
- services = FakeService.create_services(count)
-
- return mock.MagicMock(side_effect=services)
-
class FakeVolumeClient(object):
def __init__(self, **kwargs):
self.volumes = mock.Mock()
self.volumes.resource_class = fakes.FakeResource(None, {})
+ self.extensions = mock.Mock()
+ self.extensions.resource_class = fakes.FakeResource(None, {})
self.volume_snapshots = mock.Mock()
self.volume_snapshots.resource_class = fakes.FakeResource(None, {})
self.backups = mock.Mock()
@@ -428,7 +235,7 @@ class FakeVolume(object):
:param Dictionary attrs:
A dictionary with all attributes of volume
- :retrun:
+ :return:
A FakeResource object with id, name, status, etc.
"""
attrs = attrs or {}
@@ -608,6 +415,7 @@ class FakeBackup(object):
"id": 'backup-id-' + uuid.uuid4().hex,
"name": 'backup-name-' + uuid.uuid4().hex,
"volume_id": 'volume-id-' + uuid.uuid4().hex,
+ "snapshot_id": 'snapshot-id' + uuid.uuid4().hex,
"description": 'description-' + uuid.uuid4().hex,
"object_count": None,
"container": 'container-' + uuid.uuid4().hex,
@@ -642,6 +450,62 @@ class FakeBackup(object):
return backups
+ @staticmethod
+ def get_backups(backups=None, count=2):
+ """Get an iterable MagicMock object with a list of faked backups.
+
+ If backups list is provided, then initialize the Mock object with the
+ list. Otherwise create one.
+
+ :param List volumes:
+ A list of FakeResource objects faking backups
+ :param Integer count:
+ The number of backups to be faked
+ :return
+ An iterable Mock object with side_effect set to a list of faked
+ backups
+ """
+ if backups is None:
+ backups = FakeBackup.create_backups(count)
+
+ return mock.MagicMock(side_effect=backups)
+
+
+class FakeExtension(object):
+ """Fake one or more extension."""
+
+ @staticmethod
+ def create_one_extension(attrs=None):
+ """Create a fake extension.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object with name, namespace, etc.
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ extension_info = {
+ 'name': 'name-' + uuid.uuid4().hex,
+ 'namespace': ('http://docs.openstack.org/'
+ 'block-service/ext/scheduler-hints/api/v2'),
+ 'description': 'description-' + uuid.uuid4().hex,
+ 'updated': '2013-04-18T00:00:00+00:00',
+ 'alias': 'OS-SCH-HNT',
+ 'links': ('[{"href":'
+ '"https://github.com/openstack/block-api", "type":'
+ ' "text/html", "rel": "describedby"}]'),
+ }
+
+ # Overwrite default attributes.
+ extension_info.update(attrs)
+
+ extension = fakes.FakeResource(
+ info=copy.deepcopy(extension_info),
+ loaded=True)
+ return extension
+
class FakeQos(object):
"""Fake one or more Qos specification."""
@@ -717,6 +581,26 @@ class FakeQos(object):
return qoses
+ @staticmethod
+ def get_qoses(qoses=None, count=2):
+ """Get an iterable MagicMock object with a list of faked qoses.
+
+ If qoses list is provided, then initialize the Mock object with the
+ list. Otherwise create one.
+
+ :param List volumes:
+ A list of FakeResource objects faking qoses
+ :param Integer count:
+ The number of qoses to be faked
+ :return
+ An iterable Mock object with side_effect set to a list of faked
+ qoses
+ """
+ if qoses is None:
+ qoses = FakeQos.create_qoses(count)
+
+ return mock.MagicMock(side_effect=qoses)
+
class FakeSnapshot(object):
"""Fake one or more snapshot."""
@@ -770,6 +654,26 @@ class FakeSnapshot(object):
return snapshots
+ @staticmethod
+ def get_snapshots(snapshots=None, count=2):
+ """Get an iterable MagicMock object with a list of faked snapshots.
+
+ If snapshots list is provided, then initialize the Mock object with the
+ list. Otherwise create one.
+
+ :param List volumes:
+ A list of FakeResource objects faking snapshots
+ :param Integer count:
+ The number of snapshots to be faked
+ :return
+ An iterable Mock object with side_effect set to a list of faked
+ snapshots
+ """
+ if snapshots is None:
+ snapshots = FakeSnapshot.create_snapshots(count)
+
+ return mock.MagicMock(side_effect=snapshots)
+
class FakeType(object):
"""Fake one or more type."""
@@ -794,6 +698,7 @@ class FakeType(object):
"name": 'type-name-' + uuid.uuid4().hex,
"description": 'type-description-' + uuid.uuid4().hex,
"extra_specs": {"foo": "bar"},
+ "is_public": True,
}
# Overwrite default attributes.
diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py
index 8a151a9..6706435 100644
--- a/openstackclient/tests/volume/v2/test_backup.py
+++ b/openstackclient/tests/volume/v2/test_backup.py
@@ -12,6 +12,12 @@
# under the License.
#
+import mock
+from mock import call
+
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.tests.volume.v2 import fakes as volume_fakes
from openstackclient.volume.v2 import backup
@@ -25,6 +31,8 @@ class TestBackup(volume_fakes.TestVolume):
self.backups_mock.reset_mock()
self.volumes_mock = self.app.client_manager.volume.volumes
self.volumes_mock.reset_mock()
+ self.snapshots_mock = self.app.client_manager.volume.volume_snapshots
+ self.snapshots_mock.reset_mock()
self.restores_mock = self.app.client_manager.volume.restores
self.restores_mock.reset_mock()
@@ -32,8 +40,9 @@ class TestBackup(volume_fakes.TestVolume):
class TestBackupCreate(TestBackup):
volume = volume_fakes.FakeVolume.create_one_volume()
+ snapshot = volume_fakes.FakeSnapshot.create_one_snapshot()
new_backup = volume_fakes.FakeBackup.create_one_backup(
- attrs={'volume_id': volume.id})
+ attrs={'volume_id': volume.id, 'snapshot_id': snapshot.id})
columns = (
'availability_zone',
@@ -43,6 +52,7 @@ class TestBackupCreate(TestBackup):
'name',
'object_count',
'size',
+ 'snapshot_id',
'status',
'volume_id',
)
@@ -54,6 +64,7 @@ class TestBackupCreate(TestBackup):
new_backup.name,
new_backup.object_count,
new_backup.size,
+ new_backup.snapshot_id,
new_backup.status,
new_backup.volume_id,
)
@@ -62,22 +73,29 @@ class TestBackupCreate(TestBackup):
super(TestBackupCreate, self).setUp()
self.volumes_mock.get.return_value = self.volume
+ self.snapshots_mock.get.return_value = self.snapshot
self.backups_mock.create.return_value = self.new_backup
# Get the command object to test
- self.cmd = backup.CreateBackup(self.app, None)
+ self.cmd = backup.CreateVolumeBackup(self.app, None)
def test_backup_create(self):
arglist = [
"--name", self.new_backup.name,
"--description", self.new_backup.description,
"--container", self.new_backup.container,
+ "--force",
+ "--incremental",
+ "--snapshot", self.new_backup.snapshot_id,
self.new_backup.volume_id,
]
verifylist = [
("name", self.new_backup.name),
("description", self.new_backup.description),
("container", self.new_backup.container),
+ ("force", True),
+ ("incremental", True),
+ ("snapshot", self.new_backup.snapshot_id),
("volume", self.new_backup.volume_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -88,7 +106,10 @@ class TestBackupCreate(TestBackup):
self.new_backup.volume_id,
container=self.new_backup.container,
name=self.new_backup.name,
- description=self.new_backup.description
+ description=self.new_backup.description,
+ force=True,
+ incremental=True,
+ snapshot_id=self.new_backup.snapshot_id,
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -112,7 +133,10 @@ class TestBackupCreate(TestBackup):
self.new_backup.volume_id,
container=self.new_backup.container,
name=None,
- description=self.new_backup.description
+ description=self.new_backup.description,
+ force=False,
+ incremental=False,
+ snapshot_id=None,
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -120,31 +144,95 @@ class TestBackupCreate(TestBackup):
class TestBackupDelete(TestBackup):
- backup = volume_fakes.FakeBackup.create_one_backup()
+ backups = volume_fakes.FakeBackup.create_backups(count=2)
def setUp(self):
super(TestBackupDelete, self).setUp()
- self.backups_mock.get.return_value = self.backup
+ self.backups_mock.get = (
+ volume_fakes.FakeBackup.get_backups(self.backups))
self.backups_mock.delete.return_value = None
# Get the command object to mock
- self.cmd = backup.DeleteBackup(self.app, None)
+ self.cmd = backup.DeleteVolumeBackup(self.app, None)
def test_backup_delete(self):
arglist = [
- self.backup.id
+ self.backups[0].id
+ ]
+ verifylist = [
+ ("backups", [self.backups[0].id])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.backups_mock.delete.assert_called_with(
+ self.backups[0].id, False)
+ self.assertIsNone(result)
+
+ def test_backup_delete_with_force(self):
+ arglist = [
+ '--force',
+ self.backups[0].id,
]
verifylist = [
- ("backups", [self.backup.id])
+ ('force', True),
+ ("backups", [self.backups[0].id])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.backups_mock.delete.assert_called_with(self.backup.id)
+ self.backups_mock.delete.assert_called_with(self.backups[0].id, True)
self.assertIsNone(result)
+ def test_delete_multiple_backups(self):
+ arglist = []
+ for b in self.backups:
+ arglist.append(b.id)
+ verifylist = [
+ ('backups', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for b in self.backups:
+ calls.append(call(b.id, False))
+ self.backups_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_delete_multiple_backups_with_exception(self):
+ arglist = [
+ self.backups[0].id,
+ 'unexist_backup',
+ ]
+ verifylist = [
+ ('backups', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self.backups[0], exceptions.CommandError]
+ with mock.patch.object(utils, 'find_resource',
+ side_effect=find_mock_result) as find_mock:
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 backups failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(self.backups_mock, self.backups[0].id)
+ find_mock.assert_any_call(self.backups_mock, 'unexist_backup')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.backups_mock.delete.assert_called_once_with(
+ self.backups[0].id, False
+ )
+
class TestBackupList(TestBackup):
@@ -193,7 +281,7 @@ class TestBackupList(TestBackup):
self.volumes_mock.list.return_value = [self.volume]
self.backups_mock.list.return_value = self.backups
# Get the command to test
- self.cmd = backup.ListBackup(self.app, None)
+ self.cmd = backup.ListVolumeBackup(self.app, None)
def test_backup_list_without_options(self):
arglist = []
@@ -229,7 +317,7 @@ class TestBackupRestore(TestBackup):
self.volumes_mock.get.return_value = self.volume
self.restores_mock.restore.return_value = None
# Get the command object to mock
- self.cmd = backup.RestoreBackup(self.app, None)
+ self.cmd = backup.RestoreVolumeBackup(self.app, None)
def test_backup_restore(self):
arglist = [
@@ -260,6 +348,7 @@ class TestBackupShow(TestBackup):
'name',
'object_count',
'size',
+ 'snapshot_id',
'status',
'volume_id',
)
@@ -271,6 +360,7 @@ class TestBackupShow(TestBackup):
backup.name,
backup.object_count,
backup.size,
+ backup.snapshot_id,
backup.status,
backup.volume_id,
)
@@ -280,7 +370,7 @@ class TestBackupShow(TestBackup):
self.backups_mock.get.return_value = self.backup
# Get the command object to test
- self.cmd = backup.ShowBackup(self.app, None)
+ self.cmd = backup.ShowVolumeBackup(self.app, None)
def test_backup_show(self):
arglist = [
diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py
index 741f4e7..56b8ae0 100644
--- a/openstackclient/tests/volume/v2/test_qos_specs.py
+++ b/openstackclient/tests/volume/v2/test_qos_specs.py
@@ -13,7 +13,12 @@
# under the License.
#
-from openstackclient.common import utils
+import mock
+from mock import call
+
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.tests.volume.v2 import fakes as volume_fakes
from openstackclient.volume.v2 import qos_specs
@@ -155,29 +160,94 @@ class TestQosCreate(TestQos):
class TestQosDelete(TestQos):
- qos_spec = volume_fakes.FakeQos.create_one_qos()
+ qos_specs = volume_fakes.FakeQos.create_qoses(count=2)
def setUp(self):
super(TestQosDelete, self).setUp()
- self.qos_mock.get.return_value = self.qos_spec
+ self.qos_mock.get = (
+ volume_fakes.FakeQos.get_qoses(self.qos_specs))
# Get the command object to test
self.cmd = qos_specs.DeleteQos(self.app, None)
def test_qos_delete(self):
arglist = [
- self.qos_spec.id
+ self.qos_specs[0].id
]
verifylist = [
- ('qos_specs', [self.qos_spec.id])
+ ('qos_specs', [self.qos_specs[0].id])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.qos_mock.delete.assert_called_with(self.qos_spec.id)
+ self.qos_mock.delete.assert_called_with(
+ self.qos_specs[0].id, False)
self.assertIsNone(result)
+ def test_qos_delete_with_force(self):
+ arglist = [
+ '--force',
+ self.qos_specs[0].id
+ ]
+ verifylist = [
+ ('force', True),
+ ('qos_specs', [self.qos_specs[0].id])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.qos_mock.delete.assert_called_with(
+ self.qos_specs[0].id, True)
+ self.assertIsNone(result)
+
+ def test_delete_multiple_qoses(self):
+ arglist = []
+ for q in self.qos_specs:
+ arglist.append(q.id)
+ verifylist = [
+ ('qos_specs', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for q in self.qos_specs:
+ calls.append(call(q.id, False))
+ self.qos_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_delete_multiple_qoses_with_exception(self):
+ arglist = [
+ self.qos_specs[0].id,
+ 'unexist_qos',
+ ]
+ verifylist = [
+ ('qos_specs', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self.qos_specs[0], exceptions.CommandError]
+ with mock.patch.object(utils, 'find_resource',
+ side_effect=find_mock_result) as find_mock:
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual(
+ '1 of 2 QoS specifications failed to delete.', str(e))
+
+ find_mock.assert_any_call(self.qos_mock, self.qos_specs[0].id)
+ find_mock.assert_any_call(self.qos_mock, 'unexist_qos')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.qos_mock.delete.assert_called_once_with(
+ self.qos_specs[0].id, False
+ )
+
class TestQosDisassociate(TestQos):
diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py
index fe6fbb5..3eb740b 100644
--- a/openstackclient/tests/volume/v2/test_snapshot.py
+++ b/openstackclient/tests/volume/v2/test_snapshot.py
@@ -12,7 +12,13 @@
# under the License.
#
-from openstackclient.common import utils
+import argparse
+import mock
+from mock import call
+
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.tests.volume.v2 import fakes as volume_fakes
from openstackclient.volume.v2 import snapshot
@@ -69,12 +75,15 @@ class TestSnapshotCreate(TestSnapshot):
"--name", self.new_snapshot.name,
"--description", self.new_snapshot.description,
"--force",
+ '--property', 'Alpha=a',
+ '--property', 'Beta=b',
self.new_snapshot.volume_id,
]
verifylist = [
("name", self.new_snapshot.name),
("description", self.new_snapshot.description),
("force", True),
+ ('property', {'Alpha': 'a', 'Beta': 'b'}),
("volume", self.new_snapshot.volume_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -85,7 +94,8 @@ class TestSnapshotCreate(TestSnapshot):
self.new_snapshot.volume_id,
force=True,
name=self.new_snapshot.name,
- description=self.new_snapshot.description
+ description=self.new_snapshot.description,
+ metadata={'Alpha': 'a', 'Beta': 'b'},
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -109,7 +119,8 @@ class TestSnapshotCreate(TestSnapshot):
self.new_snapshot.volume_id,
force=True,
name=None,
- description=self.new_snapshot.description
+ description=self.new_snapshot.description,
+ metadata=None,
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -117,12 +128,13 @@ class TestSnapshotCreate(TestSnapshot):
class TestSnapshotDelete(TestSnapshot):
- snapshot = volume_fakes.FakeSnapshot.create_one_snapshot()
+ snapshots = volume_fakes.FakeSnapshot.create_snapshots(count=2)
def setUp(self):
super(TestSnapshotDelete, self).setUp()
- self.snapshots_mock.get.return_value = self.snapshot
+ self.snapshots_mock.get = (
+ volume_fakes.FakeSnapshot.get_snapshots(self.snapshots))
self.snapshots_mock.delete.return_value = None
# Get the command object to mock
@@ -130,18 +142,66 @@ class TestSnapshotDelete(TestSnapshot):
def test_snapshot_delete(self):
arglist = [
- self.snapshot.id
+ self.snapshots[0].id
]
verifylist = [
- ("snapshots", [self.snapshot.id])
+ ("snapshots", [self.snapshots[0].id])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.snapshots_mock.delete.assert_called_with(self.snapshot.id)
+ self.snapshots_mock.delete.assert_called_with(
+ self.snapshots[0].id)
+ self.assertIsNone(result)
+
+ def test_delete_multiple_snapshots(self):
+ arglist = []
+ for s in self.snapshots:
+ arglist.append(s.id)
+ verifylist = [
+ ('snapshots', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for s in self.snapshots:
+ calls.append(call(s.id))
+ self.snapshots_mock.delete.assert_has_calls(calls)
self.assertIsNone(result)
+ def test_delete_multiple_snapshots_with_exception(self):
+ arglist = [
+ self.snapshots[0].id,
+ 'unexist_snapshot',
+ ]
+ verifylist = [
+ ('snapshots', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self.snapshots[0], exceptions.CommandError]
+ with mock.patch.object(utils, 'find_resource',
+ side_effect=find_mock_result) as find_mock:
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 snapshots failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(
+ self.snapshots_mock, self.snapshots[0].id)
+ find_mock.assert_any_call(self.snapshots_mock, 'unexist_snapshot')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.snapshots_mock.delete.assert_called_once_with(
+ self.snapshots[0].id
+ )
+
class TestSnapshotList(TestSnapshot):
@@ -201,16 +261,33 @@ class TestSnapshotList(TestSnapshot):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
+
+ self.snapshots_mock.list.assert_called_once_with(
+ limit=None, marker=None, search_opts={'all_tenants': False})
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
def test_snapshot_list_with_options(self):
- arglist = ["--long"]
- verifylist = [("long", True), ('all_projects', False)]
+ arglist = [
+ "--long",
+ "--limit", "2",
+ "--marker", self.snapshots[0].id,
+ ]
+ verifylist = [
+ ("long", True),
+ ("limit", 2),
+ ("marker", self.snapshots[0].id),
+ ('all_projects', False),
+ ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
+ self.snapshots_mock.list.assert_called_once_with(
+ limit=2,
+ marker=self.snapshots[0].id,
+ search_opts={'all_tenants': False}
+ )
self.assertEqual(self.columns_long, columns)
self.assertEqual(self.data_long, list(data))
@@ -226,9 +303,21 @@ class TestSnapshotList(TestSnapshot):
columns, data = self.cmd.take_action(parsed_args)
+ self.snapshots_mock.list.assert_called_once_with(
+ limit=None, marker=None, search_opts={'all_tenants': True})
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_snapshot_list_negative_limit(self):
+ arglist = [
+ "--limit", "-2",
+ ]
+ verifylist = [
+ ("limit", -2),
+ ]
+ self.assertRaises(argparse.ArgumentTypeError, self.check_parser,
+ self.cmd, arglist, verifylist)
+
class TestSnapshotSet(TestSnapshot):
diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py
index 10c3861..b0316ae 100644
--- a/openstackclient/tests/volume/v2/test_type.py
+++ b/openstackclient/tests/volume/v2/test_type.py
@@ -12,10 +12,11 @@
# under the License.
#
-import copy
+import mock
+
+from osc_lib import exceptions
+from osc_lib import utils
-from openstackclient.common import utils
-from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
from openstackclient.tests import utils as tests_utils
from openstackclient.tests.volume.v2 import fakes as volume_fakes
@@ -40,9 +41,11 @@ class TestType(volume_fakes.TestVolume):
class TestTypeCreate(TestType):
+ project = identity_fakes.FakeProject.create_one_project()
columns = (
'description',
'id',
+ 'is_public',
'name',
)
@@ -53,10 +56,12 @@ class TestTypeCreate(TestType):
self.data = (
self.new_volume_type.description,
self.new_volume_type.id,
+ True,
self.new_volume_type.name,
)
self.types_mock.create.return_value = self.new_volume_type
+ self.projects_mock.get.return_value = self.project
# Get the command object to test
self.cmd = volume_type.CreateVolumeType(self.app, None)
@@ -88,12 +93,14 @@ class TestTypeCreate(TestType):
arglist = [
"--description", self.new_volume_type.description,
"--private",
+ "--project", self.project.id,
self.new_volume_type.name,
]
verifylist = [
("description", self.new_volume_type.description),
("public", False),
("private", True),
+ ("project", self.project.id),
("name", self.new_volume_type.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -108,6 +115,21 @@ class TestTypeCreate(TestType):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_public_type_create_with_project(self):
+ arglist = [
+ '--project', self.project.id,
+ self.new_volume_type.name,
+ ]
+ verifylist = [
+ ('project', self.project.id),
+ ('name', self.new_volume_type.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+
class TestTypeDelete(TestType):
@@ -127,13 +149,13 @@ class TestTypeDelete(TestType):
self.volume_type.id
]
verifylist = [
- ("volume_type", self.volume_type.id)
+ ("volume_types", [self.volume_type.id])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.types_mock.delete.assert_called_with(self.volume_type.id)
+ self.types_mock.delete.assert_called_with(self.volume_type)
self.assertIsNone(result)
@@ -175,26 +197,54 @@ class TestTypeList(TestType):
def test_type_list_without_options(self):
arglist = []
verifylist = [
- ("long", False)
+ ("long", False),
+ ("private", False),
+ ("public", False),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
+ self.types_mock.list.assert_called_once_with(is_public=None)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
def test_type_list_with_options(self):
- arglist = ["--long"]
- verifylist = [("long", True)]
+ arglist = [
+ "--long",
+ "--public",
+ ]
+ verifylist = [
+ ("long", True),
+ ("private", False),
+ ("public", True),
+ ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
+ self.types_mock.list.assert_called_once_with(is_public=True)
self.assertEqual(self.columns_long, columns)
self.assertEqual(self.data_long, list(data))
+ def test_type_list_with_private_option(self):
+ arglist = [
+ "--private",
+ ]
+ verifylist = [
+ ("long", False),
+ ("private", True),
+ ("public", False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.types_mock.list.assert_called_once_with(is_public=False)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
class TestTypeSet(TestType):
+ project = identity_fakes.FakeProject.create_one_project()
volume_type = volume_fakes.FakeType.create_one_type(
methods={'set_keys': None})
@@ -204,11 +254,7 @@ class TestTypeSet(TestType):
self.types_mock.get.return_value = self.volume_type
# Return a project
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
# Get the command object to test
self.cmd = volume_type.SetVolumeType(self.app, None)
@@ -315,11 +361,11 @@ class TestTypeSet(TestType):
def test_type_set_project_access(self):
arglist = [
- '--project', identity_fakes.project_id,
+ '--project', self.project.id,
self.volume_type.id,
]
verifylist = [
- ('project', identity_fakes.project_id),
+ ('project', self.project.id),
('volume_type', self.volume_type.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -329,15 +375,17 @@ class TestTypeSet(TestType):
self.types_access_mock.add_project_access.assert_called_with(
self.volume_type.id,
- identity_fakes.project_id,
+ self.project.id,
)
class TestTypeShow(TestType):
columns = (
+ 'access_project_ids',
'description',
'id',
+ 'is_public',
'name',
'properties',
)
@@ -347,8 +395,10 @@ class TestTypeShow(TestType):
self.volume_type = volume_fakes.FakeType.create_one_type()
self.data = (
+ None,
self.volume_type.description,
self.volume_type.id,
+ True,
self.volume_type.name,
utils.format_dict(self.volume_type.extra_specs)
)
@@ -373,9 +423,75 @@ class TestTypeShow(TestType):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_type_show_with_access(self):
+ arglist = [
+ self.volume_type.id
+ ]
+ verifylist = [
+ ("volume_type", self.volume_type.id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ private_type = volume_fakes.FakeType.create_one_type(
+ attrs={'is_public': False})
+ type_access_list = volume_fakes.FakeTypeAccess.create_one_type_access()
+ with mock.patch.object(self.types_mock, 'get',
+ return_value=private_type):
+ with mock.patch.object(self.types_access_mock, 'list',
+ return_value=[type_access_list]):
+ columns, data = self.cmd.take_action(parsed_args)
+ self.types_mock.get.assert_called_once_with(
+ self.volume_type.id)
+ self.types_access_mock.list.assert_called_once_with(
+ private_type.id)
+
+ self.assertEqual(self.columns, columns)
+ private_type_data = (
+ utils.format_list([type_access_list.project_id]),
+ private_type.description,
+ private_type.id,
+ private_type.is_public,
+ private_type.name,
+ utils.format_dict(private_type.extra_specs)
+ )
+ self.assertEqual(private_type_data, data)
+
+ def test_type_show_with_list_access_exec(self):
+ arglist = [
+ self.volume_type.id
+ ]
+ verifylist = [
+ ("volume_type", self.volume_type.id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ private_type = volume_fakes.FakeType.create_one_type(
+ attrs={'is_public': False})
+ with mock.patch.object(self.types_mock, 'get',
+ return_value=private_type):
+ with mock.patch.object(self.types_access_mock, 'list',
+ side_effect=Exception()):
+ columns, data = self.cmd.take_action(parsed_args)
+ self.types_mock.get.assert_called_once_with(
+ self.volume_type.id)
+ self.types_access_mock.list.assert_called_once_with(
+ private_type.id)
+
+ self.assertEqual(self.columns, columns)
+ private_type_data = (
+ None,
+ private_type.description,
+ private_type.id,
+ private_type.is_public,
+ private_type.name,
+ utils.format_dict(private_type.extra_specs)
+ )
+ self.assertEqual(private_type_data, data)
+
class TestTypeUnset(TestType):
+ project = identity_fakes.FakeProject.create_one_project()
volume_type = volume_fakes.FakeType.create_one_type(
methods={'unset_keys': None})
@@ -385,11 +501,7 @@ class TestTypeUnset(TestType):
self.types_mock.get.return_value = self.volume_type
# Return a project
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
# Get the command object to test
self.cmd = volume_type.UnsetVolumeType(self.app, None)
@@ -414,11 +526,11 @@ class TestTypeUnset(TestType):
def test_type_unset_project_access(self):
arglist = [
- '--project', identity_fakes.project_id,
+ '--project', self.project.id,
self.volume_type.id,
]
verifylist = [
- ('project', identity_fakes.project_id),
+ ('project', self.project.id),
('volume_type', self.volume_type.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -428,7 +540,7 @@ class TestTypeUnset(TestType):
self.types_access_mock.remove_project_access.assert_called_with(
self.volume_type.id,
- identity_fakes.project_id,
+ self.project.id,
)
def test_type_unset_not_called_without_project_argument(self):
diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py
index fb48d8a..74181a2 100644
--- a/openstackclient/tests/volume/v2/test_volume.py
+++ b/openstackclient/tests/volume/v2/test_volume.py
@@ -12,12 +12,12 @@
# under the License.
#
-import copy
-
+import mock
from mock import call
-from openstackclient.common import utils
-from openstackclient.tests import fakes
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.tests.identity.v3 import fakes as identity_fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
from openstackclient.tests.volume.v2 import fakes as volume_fakes
@@ -55,6 +55,9 @@ class TestVolume(volume_fakes.TestVolume):
class TestVolumeCreate(TestVolume):
+ project = identity_fakes.FakeProject.create_one_project()
+ user = identity_fakes.FakeUser.create_one_user()
+
columns = (
'attachments',
'availability_zone',
@@ -166,28 +169,20 @@ class TestVolumeCreate(TestVolume):
def test_volume_create_user_project_id(self):
# Return a project
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
# Return a user
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.users_mock.get.return_value = self.user
arglist = [
'--size', str(self.new_volume.size),
- '--project', identity_fakes.project_id,
- '--user', identity_fakes.user_id,
+ '--project', self.project.id,
+ '--user', self.user.id,
self.new_volume.name,
]
verifylist = [
('size', self.new_volume.size),
- ('project', identity_fakes.project_id),
- ('user', identity_fakes.user_id),
+ ('project', self.project.id),
+ ('user', self.user.id),
('name', self.new_volume.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -203,8 +198,8 @@ class TestVolumeCreate(TestVolume):
name=self.new_volume.name,
description=None,
volume_type=None,
- user_id=identity_fakes.user_id,
- project_id=identity_fakes.project_id,
+ user_id=self.user.id,
+ project_id=self.project.id,
availability_zone=None,
metadata=None,
imageRef=None,
@@ -216,28 +211,20 @@ class TestVolumeCreate(TestVolume):
def test_volume_create_user_project_name(self):
# Return a project
- self.projects_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- )
+ self.projects_mock.get.return_value = self.project
# Return a user
- self.users_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- )
+ self.users_mock.get.return_value = self.user
arglist = [
'--size', str(self.new_volume.size),
- '--project', identity_fakes.project_name,
- '--user', identity_fakes.user_name,
+ '--project', self.project.name,
+ '--user', self.user.name,
self.new_volume.name,
]
verifylist = [
('size', self.new_volume.size),
- ('project', identity_fakes.project_name),
- ('user', identity_fakes.user_name),
+ ('project', self.project.name),
+ ('user', self.user.name),
('name', self.new_volume.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -253,8 +240,8 @@ class TestVolumeCreate(TestVolume):
name=self.new_volume.name,
description=None,
volume_type=None,
- user_id=identity_fakes.user_id,
- project_id=identity_fakes.project_id,
+ user_id=self.user.id,
+ project_id=self.project.id,
availability_zone=None,
metadata=None,
imageRef=None,
@@ -433,13 +420,16 @@ class TestVolumeDelete(TestVolume):
volumes[0].id
]
verifylist = [
- ("volumes", [volumes[0].id])
+ ("force", False),
+ ("purge", False),
+ ("volumes", [volumes[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.volumes_mock.delete.assert_called_with(volumes[0].id)
+ self.volumes_mock.delete.assert_called_once_with(
+ volumes[0].id, cascade=False)
self.assertIsNone(result)
def test_volume_delete_multi_volumes(self):
@@ -447,19 +437,94 @@ class TestVolumeDelete(TestVolume):
arglist = [v.id for v in volumes]
verifylist = [
+ ('force', False),
+ ('purge', False),
('volumes', arglist),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- calls = [call(v.id) for v in volumes]
+ calls = [call(v.id, cascade=False) for v in volumes]
self.volumes_mock.delete.assert_has_calls(calls)
self.assertIsNone(result)
+ def test_volume_delete_multi_volumes_with_exception(self):
+ volumes = self.setup_volumes_mock(count=2)
+
+ arglist = [
+ volumes[0].id,
+ 'unexist_volume',
+ ]
+ verifylist = [
+ ('force', False),
+ ('purge', False),
+ ('volumes', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [volumes[0], exceptions.CommandError]
+ with mock.patch.object(utils, 'find_resource',
+ side_effect=find_mock_result) as find_mock:
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 volumes failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(self.volumes_mock, volumes[0].id)
+ find_mock.assert_any_call(self.volumes_mock, 'unexist_volume')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.volumes_mock.delete.assert_called_once_with(
+ volumes[0].id, cascade=False)
+
+ def test_volume_delete_with_purge(self):
+ volumes = self.setup_volumes_mock(count=1)
+
+ arglist = [
+ '--purge',
+ volumes[0].id,
+ ]
+ verifylist = [
+ ('force', False),
+ ('purge', True),
+ ('volumes', [volumes[0].id]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.volumes_mock.delete.assert_called_once_with(
+ volumes[0].id, cascade=True)
+ self.assertIsNone(result)
+
+ def test_volume_delete_with_force(self):
+ volumes = self.setup_volumes_mock(count=1)
+
+ arglist = [
+ '--force',
+ volumes[0].id,
+ ]
+ verifylist = [
+ ('force', True),
+ ('purge', False),
+ ('volumes', [volumes[0].id]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.volumes_mock.force_delete.assert_called_once_with(volumes[0].id)
+ self.assertIsNone(result)
+
class TestVolumeList(TestVolume):
+ project = identity_fakes.FakeProject.create_one_project()
+ user = identity_fakes.FakeUser.create_one_user()
+
columns = [
'ID',
'Display Name',
@@ -474,21 +539,9 @@ class TestVolumeList(TestVolume):
self.mock_volume = volume_fakes.FakeVolume.create_one_volume()
self.volumes_mock.list.return_value = [self.mock_volume]
- self.users_mock.get.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.USER),
- loaded=True,
- ),
- ]
+ self.users_mock.get.return_value = self.user
- self.projects_mock.get.return_value = [
- fakes.FakeResource(
- None,
- copy.deepcopy(identity_fakes.PROJECT),
- loaded=True,
- ),
- ]
+ self.projects_mock.get.return_value = self.project
# Get the command object to test
self.cmd = volume.ListVolume(self.app, None)
@@ -521,10 +574,10 @@ class TestVolumeList(TestVolume):
def test_volume_list_project(self):
arglist = [
- '--project', identity_fakes.project_name,
+ '--project', self.project.name,
]
verifylist = [
- ('project', identity_fakes.project_name),
+ ('project', self.project.name),
('long', False),
('all_projects', False),
('status', None),
@@ -549,12 +602,12 @@ class TestVolumeList(TestVolume):
def test_volume_list_project_domain(self):
arglist = [
- '--project', identity_fakes.project_name,
- '--project-domain', identity_fakes.domain_name,
+ '--project', self.project.name,
+ '--project-domain', self.project.domain_id,
]
verifylist = [
- ('project', identity_fakes.project_name),
- ('project_domain', identity_fakes.domain_name),
+ ('project', self.project.name),
+ ('project_domain', self.project.domain_id),
('long', False),
('all_projects', False),
('status', None),
@@ -579,10 +632,10 @@ class TestVolumeList(TestVolume):
def test_volume_list_user(self):
arglist = [
- '--user', identity_fakes.user_name,
+ '--user', self.user.name,
]
verifylist = [
- ('user', identity_fakes.user_name),
+ ('user', self.user.name),
('long', False),
('all_projects', False),
('status', None),
@@ -606,12 +659,12 @@ class TestVolumeList(TestVolume):
def test_volume_list_user_domain(self):
arglist = [
- '--user', identity_fakes.user_name,
- '--user-domain', identity_fakes.domain_name,
+ '--user', self.user.name,
+ '--user-domain', self.user.domain_id,
]
verifylist = [
- ('user', identity_fakes.user_name),
- ('user_domain', identity_fakes.domain_name),
+ ('user', self.user.name),
+ ('user_domain', self.user.domain_id),
('long', False),
('all_projects', False),
('status', None),
@@ -761,6 +814,74 @@ class TestVolumeList(TestVolume):
self.assertEqual(datalist, tuple(data))
+class TestVolumeSet(TestVolume):
+
+ def setUp(self):
+ super(TestVolumeSet, self).setUp()
+
+ self.new_volume = volume_fakes.FakeVolume.create_one_volume()
+ self.volumes_mock.get.return_value = self.new_volume
+
+ # Get the command object to test
+ self.cmd = volume.SetVolume(self.app, None)
+
+ def test_volume_set_image_property(self):
+ arglist = [
+ '--image-property', 'Alpha=a',
+ '--image-property', 'Beta=b',
+ self.new_volume.id,
+ ]
+ verifylist = [
+ ('image_property', {'Alpha': 'a', 'Beta': 'b'}),
+ ('volume', self.new_volume.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class ShowOne in cliff, abstract method take_action()
+ # returns nothing
+ self.cmd.take_action(parsed_args)
+ self.volumes_mock.set_image_metadata.assert_called_with(
+ self.new_volume.id, parsed_args.image_property)
+
+ def test_volume_set_state(self):
+ arglist = [
+ '--state', 'error',
+ self.new_volume.id
+ ]
+ verifylist = [
+ ('state', 'error'),
+ ('volume', self.new_volume.id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.volumes_mock.reset_state.assert_called_with(
+ self.new_volume.id, 'error')
+ self.assertIsNone(result)
+
+ def test_volume_set_state_failed(self):
+ self.volumes_mock.reset_state.side_effect = exceptions.CommandError()
+ arglist = [
+ '--state', 'error',
+ self.new_volume.id
+ ]
+ verifylist = [
+ ('state', 'error'),
+ ('volume', self.new_volume.id)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('One or more of the set operations failed',
+ str(e))
+ self.volumes_mock.reset_state.assert_called_with(
+ self.new_volume.id, 'error')
+
+
class TestVolumeShow(TestVolume):
def setUp(self):
@@ -792,43 +913,13 @@ class TestVolumeShow(TestVolume):
data)
-class TestVolumeSet(TestVolume):
-
- def setUp(self):
- super(TestVolumeSet, self).setUp()
-
- self.new_volume = volume_fakes.FakeVolume.create_one_volume()
- self.volumes_mock.create.return_value = self.new_volume
-
- # Get the command object to test
- self.cmd = volume.SetVolume(self.app, None)
-
- def test_volume_set_image_property(self):
- arglist = [
- '--image-property', 'Alpha=a',
- '--image-property', 'Beta=b',
- self.new_volume.id,
- ]
- verifylist = [
- ('image_property', {'Alpha': 'a', 'Beta': 'b'}),
- ('volume', self.new_volume.id),
- ]
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- # In base command class ShowOne in cliff, abstract method take_action()
- # returns nothing
- self.cmd.take_action(parsed_args)
- self.volumes_mock.set_image_metadata.assert_called_with(
- self.volumes_mock.get().id, parsed_args.image_property)
-
-
class TestVolumeUnset(TestVolume):
def setUp(self):
super(TestVolumeUnset, self).setUp()
self.new_volume = volume_fakes.FakeVolume.create_one_volume()
- self.volumes_mock.create.return_value = self.new_volume
+ self.volumes_mock.get.return_value = self.new_volume
# Get the command object to set property
self.cmd_set = volume.SetVolume(self.app, None)
@@ -872,4 +963,4 @@ class TestVolumeUnset(TestVolume):
self.cmd_unset.take_action(parsed_args_unset)
self.volumes_mock.delete_image_metadata.assert_called_with(
- self.volumes_mock.get().id, parsed_args_unset.image_property)
+ self.new_volume.id, parsed_args_unset.image_property)
diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py
index a60f4b0..ade5a95 100644
--- a/openstackclient/volume/client.py
+++ b/openstackclient/volume/client.py
@@ -1,4 +1,4 @@
-# Copyright 2012-2013 OpenStack, LLC.
+# Copyright 2012-2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -15,9 +15,11 @@
import logging
-from openstackclient.common import utils
+from osc_lib import utils
+
from openstackclient.i18n import _
+
LOG = logging.getLogger(__name__)
DEFAULT_API_VERSION = '2'
@@ -55,13 +57,13 @@ def make_client(instance):
extensions = [extension.Extension('list_extensions', list_extensions)]
# Remember interface only if it is set
- kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface)
+ kwargs = utils.build_kwargs_dict('endpoint_type', instance.interface)
client = volume_client(
session=instance.session,
extensions=extensions,
http_log_debug=http_log_debug,
- region_name=instance._region_name,
+ region_name=instance.region_name,
**kwargs
)
diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py
index 607b521..539ed36 100644
--- a/openstackclient/volume/v1/backup.py
+++ b/openstackclient/volume/v1/backup.py
@@ -16,18 +16,20 @@
"""Volume v1 Backup action implementations"""
import copy
+import logging
+
+from osc_lib.command import command
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
-class CreateBackup(command.ShowOne):
- """Create new backup"""
+class CreateVolumeBackup(command.ShowOne):
+ """Create new volume backup"""
def get_parser(self, prog_name):
- parser = super(CreateBackup, self).get_parser(prog_name)
+ parser = super(CreateVolumeBackup, self).get_parser(prog_name)
parser.add_argument(
'volume',
metavar='<volume>',
@@ -66,16 +68,33 @@ class CreateBackup(command.ShowOne):
return zip(*sorted(six.iteritems(backup._info)))
-class DeleteBackup(command.Command):
- """Delete backup(s)"""
+class CreateBackup(CreateVolumeBackup):
+ """Create new backup"""
+
+ # TODO(Huanxuan Ao): Remove this class and ``backup create`` command
+ # two cycles after Newton.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "volume backup create" instead.'))
+ return super(CreateBackup, self).take_action(parsed_args)
+
+
+class DeleteVolumeBackup(command.Command):
+ """Delete volume backup(s)"""
def get_parser(self, prog_name):
- parser = super(DeleteBackup, self).get_parser(prog_name)
+ parser = super(DeleteVolumeBackup, self).get_parser(prog_name)
parser.add_argument(
'backups',
metavar='<backup>',
nargs="+",
- help=_('Backup(s) to delete (ID only)'),
+ help=_('Backup(s) to delete (name or ID)'),
)
return parser
@@ -87,11 +106,28 @@ class DeleteBackup(command.Command):
volume_client.backups.delete(backup_id)
-class ListBackup(command.Lister):
- """List backups"""
+class DeleteBackup(DeleteVolumeBackup):
+ """Delete backup(s)"""
+
+ # TODO(Huanxuan Ao): Remove this class and ``backup delete`` command
+ # two cycles after Newton.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "volume backup delete" instead.'))
+ return super(DeleteBackup, self).take_action(parsed_args)
+
+
+class ListVolumeBackup(command.Lister):
+ """List volume backups"""
def get_parser(self, prog_name):
- parser = super(ListBackup, self).get_parser(prog_name)
+ parser = super(ListVolumeBackup, self).get_parser(prog_name)
parser.add_argument(
'--long',
action='store_true',
@@ -141,15 +177,32 @@ class ListBackup(command.Lister):
) for s in data))
-class RestoreBackup(command.Command):
- """Restore backup"""
+class ListBackup(ListVolumeBackup):
+ """List backups"""
+
+ # TODO(Huanxuan Ao): Remove this class and ``backup list`` command
+ # two cycles after Newton.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "volume backup list" instead.'))
+ return super(ListBackup, self).take_action(parsed_args)
+
+
+class RestoreVolumeBackup(command.Command):
+ """Restore volume backup"""
def get_parser(self, prog_name):
- parser = super(RestoreBackup, self).get_parser(prog_name)
+ parser = super(RestoreVolumeBackup, self).get_parser(prog_name)
parser.add_argument(
'backup',
metavar='<backup>',
- help=_('Backup to restore (ID only)')
+ help=_('Backup to restore (name or ID)')
)
parser.add_argument(
'volume',
@@ -168,15 +221,32 @@ class RestoreBackup(command.Command):
destination_volume.id)
-class ShowBackup(command.ShowOne):
- """Display backup details"""
+class RestoreBackup(RestoreVolumeBackup):
+ """Restore backup"""
+
+ # TODO(Huanxuan Ao): Remove this class and ``backup restore`` command
+ # two cycles after Newton.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "volume backup restore" instead.'))
+ return super(RestoreBackup, self).take_action(parsed_args)
+
+
+class ShowVolumeBackup(command.ShowOne):
+ """Display volume backup details"""
def get_parser(self, prog_name):
- parser = super(ShowBackup, self).get_parser(prog_name)
+ parser = super(ShowVolumeBackup, self).get_parser(prog_name)
parser.add_argument(
'backup',
metavar='<backup>',
- help=_('Backup to display (ID only)')
+ help=_('Backup to display (name or ID)')
)
return parser
@@ -186,3 +256,20 @@ class ShowBackup(command.ShowOne):
parsed_args.backup)
backup._info.pop('links')
return zip(*sorted(six.iteritems(backup._info)))
+
+
+class ShowBackup(ShowVolumeBackup):
+ """Display backup details"""
+
+ # TODO(Huanxuan Ao): Remove this class and ``backup show`` command
+ # two cycles after Newton.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "volume backup show" instead.'))
+ return super(ShowBackup, self).take_action(parsed_args)
diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py
index c49477a..c585087 100644
--- a/openstackclient/volume/v1/qos_specs.py
+++ b/openstackclient/volume/v1/qos_specs.py
@@ -15,11 +15,11 @@
"""Volume v1 QoS action implementations"""
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
@@ -103,13 +103,19 @@ class DeleteQos(command.Command):
nargs="+",
help=_('QoS specification(s) to delete (name or ID)'),
)
+ parser.add_argument(
+ '--force',
+ action='store_true',
+ default=False,
+ help=_("Allow to delete in-use QoS specification(s)")
+ )
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
for qos in parsed_args.qos_specs:
qos_spec = utils.find_resource(volume_client.qos_specs, qos)
- volume_client.qos_specs.delete(qos_spec.id)
+ volume_client.qos_specs.delete(qos_spec.id, parsed_args.force)
class DisassociateQos(command.Command):
diff --git a/openstackclient/volume/v1/service.py b/openstackclient/volume/v1/service.py
index 023dda9..2df3857 100644
--- a/openstackclient/volume/v1/service.py
+++ b/openstackclient/volume/v1/service.py
@@ -14,8 +14,9 @@
"""Service action implementations"""
-from openstackclient.common import command
-from openstackclient.common import utils
+from osc_lib.command import command
+from osc_lib import utils
+
from openstackclient.i18n import _
diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py
index 5132d71..bb3a1fc 100644
--- a/openstackclient/volume/v1/snapshot.py
+++ b/openstackclient/volume/v1/snapshot.py
@@ -1,4 +1,4 @@
-# Copyright 2012-2013 OpenStack, LLC.
+# Copyright 2012-2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -16,11 +16,12 @@
"""Volume v1 Snapshot action implementations"""
import copy
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py
index ffec180..820673b 100644
--- a/openstackclient/volume/v1/volume.py
+++ b/openstackclient/volume/v1/volume.py
@@ -16,14 +16,19 @@
"""Volume v1 Volume action implementations"""
import argparse
+import logging
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateVolume(command.ShowOne):
"""Create new volume"""
@@ -170,7 +175,6 @@ class DeleteVolume(command.Command):
)
parser.add_argument(
'--force',
- dest='force',
action='store_true',
default=False,
help=_('Attempt forced removal of volume(s), regardless of state '
@@ -342,13 +346,12 @@ class SetVolume(command.Command):
if parsed_args.size:
if volume.status != 'available':
- self.app.log.error(_("Volume is in %s state, it must be "
- "available before size can be extended") %
- volume.status)
+ LOG.error(_("Volume is in %s state, it must be available "
+ "before size can be extended"), volume.status)
return
if parsed_args.size <= volume.size:
- self.app.log.error(_("New size must be greater than %s GB") %
- volume.size)
+ LOG.error(_("New size must be greater than %s GB"),
+ volume.size)
return
volume_client.volumes.extend(volume.id, parsed_args.size)
diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py
index 98689e7..5d8ff68 100644
--- a/openstackclient/volume/v1/volume_transfer_request.py
+++ b/openstackclient/volume/v1/volume_transfer_request.py
@@ -14,9 +14,9 @@
"""Volume v2 transfer action implementations"""
+from osc_lib.command import command
+from osc_lib import utils
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py
index 4e9b192..3fe4fa0 100644
--- a/openstackclient/volume/v1/volume_type.py
+++ b/openstackclient/volume/v1/volume_type.py
@@ -15,14 +15,20 @@
"""Volume v1 Type action implementations"""
+import logging
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateVolumeType(command.ShowOne):
"""Create new volume type"""
@@ -56,22 +62,39 @@ class CreateVolumeType(command.ShowOne):
class DeleteVolumeType(command.Command):
- """Delete volume type"""
+ """Delete volume type(s)"""
def get_parser(self, prog_name):
parser = super(DeleteVolumeType, self).get_parser(prog_name)
parser.add_argument(
- 'volume_type',
+ 'volume_types',
metavar='<volume-type>',
- help=_('Volume type to delete (name or ID)'),
+ nargs='+',
+ help=_('Volume type(s) to delete (name or ID)'),
)
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
- volume_type_id = utils.find_resource(
- volume_client.volume_types, parsed_args.volume_type).id
- volume_client.volume_types.delete(volume_type_id)
+ result = 0
+
+ for volume_type in parsed_args.volume_types:
+ try:
+ vol_type = utils.find_resource(volume_client.volume_types,
+ volume_type)
+
+ volume_client.volume_types.delete(vol_type)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete volume type with "
+ "name or ID '%(volume_type)s': %(e)s")
+ % {'volume_type': volume_type, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.volume_types)
+ msg = (_("%(result)s of %(total)s volume types failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListVolumeType(command.Lister):
diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py
index e6fbe78..07c1c94 100644
--- a/openstackclient/volume/v2/backup.py
+++ b/openstackclient/volume/v2/backup.py
@@ -15,19 +15,24 @@
"""Volume v2 Backup action implementations"""
import copy
+import logging
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
-class CreateBackup(command.ShowOne):
- """Create new backup"""
+LOG = logging.getLogger(__name__)
+
+
+class CreateVolumeBackup(command.ShowOne):
+ """Create new volume backup"""
def get_parser(self, prog_name):
- parser = super(CreateBackup, self).get_parser(prog_name)
+ parser = super(CreateVolumeBackup, self).get_parser(prog_name)
parser.add_argument(
"volume",
metavar="<volume>",
@@ -48,48 +53,126 @@ class CreateBackup(command.ShowOne):
metavar="<container>",
help=_("Optional backup container name")
)
+ parser.add_argument(
+ "--snapshot",
+ metavar="<snapshot>",
+ help=_("Snapshot to backup (name or ID)")
+ )
+ parser.add_argument(
+ '--force',
+ action='store_true',
+ default=False,
+ help=_("Allow to back up an in-use volume")
+ )
+ parser.add_argument(
+ '--incremental',
+ action='store_true',
+ default=False,
+ help=_("Perform an incremental backup")
+ )
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
volume_id = utils.find_resource(
volume_client.volumes, parsed_args.volume).id
+ snapshot_id = None
+ if parsed_args.snapshot:
+ snapshot_id = utils.find_resource(
+ volume_client.volume_snapshots, parsed_args.snapshot).id
backup = volume_client.backups.create(
volume_id,
container=parsed_args.container,
name=parsed_args.name,
- description=parsed_args.description
+ description=parsed_args.description,
+ force=parsed_args.force,
+ incremental=parsed_args.incremental,
+ snapshot_id=snapshot_id,
)
backup._info.pop("links", None)
return zip(*sorted(six.iteritems(backup._info)))
-class DeleteBackup(command.Command):
- """Delete backup(s)"""
+class CreateBackup(CreateVolumeBackup):
+ """Create new backup"""
+
+ # TODO(Huanxuan Ao): Remove this class and ``backup create`` command
+ # two cycles after Newton.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "volume backup create" instead.'))
+ return super(CreateBackup, self).take_action(parsed_args)
+
+
+class DeleteVolumeBackup(command.Command):
+ """Delete volume backup(s)"""
def get_parser(self, prog_name):
- parser = super(DeleteBackup, self).get_parser(prog_name)
+ parser = super(DeleteVolumeBackup, self).get_parser(prog_name)
parser.add_argument(
"backups",
metavar="<backup>",
nargs="+",
help=_("Backup(s) to delete (name or ID)")
)
+ parser.add_argument(
+ '--force',
+ action='store_true',
+ default=False,
+ help=_("Allow delete in state other than error or available")
+ )
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
- for backup in parsed_args.backups:
- backup_id = utils.find_resource(
- volume_client.backups, backup).id
- volume_client.backups.delete(backup_id)
+ result = 0
+ for i in parsed_args.backups:
+ try:
+ backup_id = utils.find_resource(
+ volume_client.backups, i).id
+ volume_client.backups.delete(backup_id, parsed_args.force)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete backup with "
+ "name or ID '%(backup)s': %(e)s")
+ % {'backup': i, 'e': e})
-class ListBackup(command.Lister):
- """List backups"""
+ if result > 0:
+ total = len(parsed_args.backups)
+ msg = (_("%(result)s of %(total)s backups failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class DeleteBackup(DeleteVolumeBackup):
+ """Delete backup(s)"""
+
+ # TODO(Huanxuan Ao): Remove this class and ``backup delete`` command
+ # two cycles after Newton.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "volume backup delete" instead.'))
+ return super(DeleteBackup, self).take_action(parsed_args)
+
+
+class ListVolumeBackup(command.Lister):
+ """List volume backups"""
def get_parser(self, prog_name):
- parser = super(ListBackup, self).get_parser(prog_name)
+ parser = super(ListVolumeBackup, self).get_parser(prog_name)
parser.add_argument(
"--long",
action="store_true",
@@ -139,15 +222,32 @@ class ListBackup(command.Lister):
) for s in data))
-class RestoreBackup(command.ShowOne):
- """Restore backup"""
+class ListBackup(ListVolumeBackup):
+ """List backups"""
+
+ # TODO(Huanxuan Ao): Remove this class and ``backup list`` command
+ # two cycles after Newton.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "volume backup list" instead.'))
+ return super(ListBackup, self).take_action(parsed_args)
+
+
+class RestoreVolumeBackup(command.ShowOne):
+ """Restore volume backup"""
def get_parser(self, prog_name):
- parser = super(RestoreBackup, self).get_parser(prog_name)
+ parser = super(RestoreVolumeBackup, self).get_parser(prog_name)
parser.add_argument(
"backup",
metavar="<backup>",
- help=_("Backup to restore (ID only)")
+ help=_("Backup to restore (name or ID)")
)
parser.add_argument(
"volume",
@@ -164,11 +264,28 @@ class RestoreBackup(command.ShowOne):
return volume_client.restores.restore(backup.id, destination_volume.id)
-class ShowBackup(command.ShowOne):
- """Display backup details"""
+class RestoreBackup(RestoreVolumeBackup):
+ """Restore backup"""
+
+ # TODO(Huanxuan Ao): Remove this class and ``backup restore`` command
+ # two cycles after Newton.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "volume backup restore" instead.'))
+ return super(RestoreBackup, self).take_action(parsed_args)
+
+
+class ShowVolumeBackup(command.ShowOne):
+ """Display volume backup details"""
def get_parser(self, prog_name):
- parser = super(ShowBackup, self).get_parser(prog_name)
+ parser = super(ShowVolumeBackup, self).get_parser(prog_name)
parser.add_argument(
"backup",
metavar="<backup>",
@@ -182,3 +299,20 @@ class ShowBackup(command.ShowOne):
parsed_args.backup)
backup._info.pop("links", None)
return zip(*sorted(six.iteritems(backup._info)))
+
+
+class ShowBackup(ShowVolumeBackup):
+ """Display backup details"""
+
+ # TODO(Huanxuan Ao): Remove this class and ``backup show`` command
+ # two cycles after Newton.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action(self, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "volume backup show" instead.'))
+ return super(ShowBackup, self).take_action(parsed_args)
diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py
index 90e11c7..9797f1a 100644
--- a/openstackclient/volume/v2/qos_specs.py
+++ b/openstackclient/volume/v2/qos_specs.py
@@ -15,14 +15,20 @@
"""Volume v2 QoS action implementations"""
+import logging
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class AssociateQos(command.Command):
"""Associate a QoS specification to a volume type"""
@@ -103,13 +109,33 @@ class DeleteQos(command.Command):
nargs="+",
help=_('QoS specification(s) to delete (name or ID)'),
)
+ parser.add_argument(
+ '--force',
+ action='store_true',
+ default=False,
+ help=_("Allow to delete in-use QoS specification(s)")
+ )
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
- for qos in parsed_args.qos_specs:
- qos_spec = utils.find_resource(volume_client.qos_specs, qos)
- volume_client.qos_specs.delete(qos_spec.id)
+ result = 0
+
+ for i in parsed_args.qos_specs:
+ try:
+ qos_spec = utils.find_resource(volume_client.qos_specs, i)
+ volume_client.qos_specs.delete(qos_spec.id, parsed_args.force)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete QoS specification with "
+ "name or ID '%(qos)s': %(e)s")
+ % {'qos': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.qos_specs)
+ msg = (_("%(result)s of %(total)s QoS specifications failed"
+ " to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class DisassociateQos(command.Command):
diff --git a/openstackclient/volume/v2/service.py b/openstackclient/volume/v2/service.py
index 023dda9..2df3857 100644
--- a/openstackclient/volume/v2/service.py
+++ b/openstackclient/volume/v2/service.py
@@ -14,8 +14,9 @@
"""Service action implementations"""
-from openstackclient.common import command
-from openstackclient.common import utils
+from osc_lib.command import command
+from osc_lib import utils
+
from openstackclient.i18n import _
diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py
index d901708..8304a5e 100644
--- a/openstackclient/volume/v2/snapshot.py
+++ b/openstackclient/volume/v2/snapshot.py
@@ -15,15 +15,20 @@
"""Volume v2 snapshot action implementations"""
import copy
+import logging
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateSnapshot(command.ShowOne):
"""Create new snapshot"""
@@ -46,12 +51,18 @@ class CreateSnapshot(command.ShowOne):
)
parser.add_argument(
"--force",
- dest="force",
action="store_true",
default=False,
help=_("Create a snapshot attached to an instance. "
"Default is False")
)
+ parser.add_argument(
+ "--property",
+ metavar="<key=value>",
+ action=parseractions.KeyValueAction,
+ help=_("Set a property to this snapshot "
+ "(repeat option to set multiple properties)"),
+ )
return parser
def take_action(self, parsed_args):
@@ -62,7 +73,8 @@ class CreateSnapshot(command.ShowOne):
volume_id,
force=parsed_args.force,
name=parsed_args.name,
- description=parsed_args.description
+ description=parsed_args.description,
+ metadata=parsed_args.property,
)
snapshot._info.update(
{'properties': utils.format_dict(snapshot._info.pop('metadata'))}
@@ -85,10 +97,24 @@ class DeleteSnapshot(command.Command):
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
- for snapshot in parsed_args.snapshots:
- snapshot_id = utils.find_resource(
- volume_client.volume_snapshots, snapshot).id
- volume_client.volume_snapshots.delete(snapshot_id)
+ result = 0
+
+ for i in parsed_args.snapshots:
+ try:
+ snapshot_id = utils.find_resource(
+ volume_client.volume_snapshots, i).id
+ volume_client.volume_snapshots.delete(snapshot_id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete snapshot with "
+ "name or ID '%(snapshot)s': %(e)s")
+ % {'snapshot': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.snapshots)
+ msg = (_("%(result)s of %(total)s snapshots failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListSnapshot(command.Lister):
@@ -108,6 +134,18 @@ class ListSnapshot(command.Lister):
default=False,
help=_('List additional fields in output'),
)
+ parser.add_argument(
+ '--marker',
+ metavar='<marker>',
+ help=_('The last snapshot ID of the previous page'),
+ )
+ parser.add_argument(
+ '--limit',
+ type=int,
+ action=parseractions.NonNegativeAction,
+ metavar='<limit>',
+ help=_('Maximum number of snapshots to display'),
+ )
return parser
def take_action(self, parsed_args):
@@ -148,7 +186,10 @@ class ListSnapshot(command.Lister):
}
data = self.app.client_manager.volume.volume_snapshots.list(
- search_opts=search_opts)
+ search_opts=search_opts,
+ marker=parsed_args.marker,
+ limit=parsed_args.limit,
+ )
return (column_headers,
(utils.get_item_properties(
s, columns,
diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py
index 18473da..bd201e0 100644
--- a/openstackclient/volume/v2/volume.py
+++ b/openstackclient/volume/v2/volume.py
@@ -15,16 +15,21 @@
"""Volume V2 Volume action implementations"""
import copy
+import logging
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+LOG = logging.getLogger(__name__)
+
+
class CreateVolume(command.ShowOne):
"""Create new volume"""
@@ -161,25 +166,45 @@ class DeleteVolume(command.Command):
nargs="+",
help=_("Volume(s) to delete (name or ID)")
)
- parser.add_argument(
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument(
"--force",
- dest="force",
action="store_true",
- default=False,
help=_("Attempt forced removal of volume(s), regardless of state "
"(defaults to False)")
)
+ group.add_argument(
+ "--purge",
+ action="store_true",
+ help=_("Remove any snapshots along with volume(s) "
+ "(defaults to False)")
+ )
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
- for volume in parsed_args.volumes:
- volume_obj = utils.find_resource(
- volume_client.volumes, volume)
- if parsed_args.force:
- volume_client.volumes.force_delete(volume_obj.id)
- else:
- volume_client.volumes.delete(volume_obj.id)
+ result = 0
+
+ for i in parsed_args.volumes:
+ try:
+ volume_obj = utils.find_resource(
+ volume_client.volumes, i)
+ if parsed_args.force:
+ volume_client.volumes.force_delete(volume_obj.id)
+ else:
+ volume_client.volumes.delete(volume_obj.id,
+ cascade=parsed_args.purge)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete volume with "
+ "name or ID '%(volume)s': %(e)s"),
+ {'volume': i, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.volumes)
+ msg = (_("%(result)s of %(total)s volumes failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListVolume(command.Lister):
@@ -353,29 +378,58 @@ class SetVolume(command.Command):
help=_('Set an image property on this volume '
'(repeat option to set multiple image properties)'),
)
+ parser.add_argument(
+ "--state",
+ metavar="<state>",
+ choices=['available', 'error', 'creating', 'deleting',
+ 'in-use', 'attaching', 'detaching', 'error_deleting',
+ 'maintenance'],
+ help=_('New volume state ("available", "error", "creating", '
+ '"deleting", "in-use", "attaching", "detaching", '
+ '"error_deleting" or "maintenance")'),
+ )
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
+ result = 0
if parsed_args.size:
- if volume.status != 'available':
- self.app.log.error(_("Volume is in %s state, it must be "
- "available before size can be extended") %
- volume.status)
- return
- if parsed_args.size <= volume.size:
- self.app.log.error(_("New size must be greater than %s GB") %
- volume.size)
- return
- volume_client.volumes.extend(volume.id, parsed_args.size)
+ try:
+ if volume.status != 'available':
+ msg = (_("Volume is in %s state, it must be available "
+ "before size can be extended"), volume.status)
+ raise exceptions.CommandError(msg)
+ if parsed_args.size <= volume.size:
+ msg = _("New size must be greater than %s GB"), volume.size
+ raise exceptions.CommandError(msg)
+ volume_client.volumes.extend(volume.id, parsed_args.size)
+ except Exception as e:
+ LOG.error(_("Failed to set volume size: %s"), e)
+ result += 1
if parsed_args.property:
- volume_client.volumes.set_metadata(volume.id, parsed_args.property)
+ try:
+ volume_client.volumes.set_metadata(
+ volume.id, parsed_args.property)
+ except Exception as e:
+ LOG.error(_("Failed to set volume property: %s"), e)
+ result += 1
if parsed_args.image_property:
- volume_client.volumes.set_image_metadata(
- volume.id, parsed_args.image_property)
+ try:
+ volume_client.volumes.set_image_metadata(
+ volume.id, parsed_args.image_property)
+ except Exception as e:
+ LOG.error(_("Failed to set image property: %s"), e)
+ result += 1
+ if parsed_args.state:
+ try:
+ volume_client.volumes.reset_state(
+ volume.id, parsed_args.state)
+ except Exception as e:
+ LOG.error(_("Failed to set volume state: %s"), e)
+ result += 1
kwargs = {}
if parsed_args.name:
@@ -383,7 +437,16 @@ class SetVolume(command.Command):
if parsed_args.description:
kwargs['display_description'] = parsed_args.description
if kwargs:
- volume_client.volumes.update(volume.id, **kwargs)
+ try:
+ volume_client.volumes.update(volume.id, **kwargs)
+ except Exception as e:
+ LOG.error(_("Failed to update volume display name "
+ "or display description: %s"), e)
+ result += 1
+
+ if result > 0:
+ raise exceptions.CommandError(_("One or more of the "
+ "set operations failed"))
class ShowVolume(command.ShowOne):
@@ -454,6 +517,3 @@ class UnsetVolume(command.Command):
if parsed_args.image_property:
volume_client.volumes.delete_image_metadata(
volume.id, parsed_args.image_property)
-
- if (not parsed_args.image_property and not parsed_args.property):
- self.app.log.error(_("No changes requested\n"))
diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py
index 98689e7..5d8ff68 100644
--- a/openstackclient/volume/v2/volume_transfer_request.py
+++ b/openstackclient/volume/v2/volume_transfer_request.py
@@ -14,9 +14,9 @@
"""Volume v2 transfer action implementations"""
+from osc_lib.command import command
+from osc_lib import utils
-from openstackclient.common import command
-from openstackclient.common import utils
from openstackclient.i18n import _
diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py
index 9aed17b..e42fffe 100644
--- a/openstackclient/volume/v2/volume_type.py
+++ b/openstackclient/volume/v2/volume_type.py
@@ -14,16 +14,21 @@
"""Volume v2 Type action implementations"""
+import logging
+
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
import six
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import parseractions
-from openstackclient.common import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+LOG = logging.getLogger(__name__)
+
+
class CreateVolumeType(command.ShowOne):
"""Create new volume type"""
@@ -42,14 +47,12 @@ class CreateVolumeType(command.ShowOne):
public_group = parser.add_mutually_exclusive_group()
public_group.add_argument(
"--public",
- dest="public",
action="store_true",
default=False,
help=_("Volume type is accessible to the public"),
)
public_group.add_argument(
"--private",
- dest="private",
action="store_true",
default=False,
help=_("Volume type is not accessible to the public"),
@@ -61,12 +64,23 @@ class CreateVolumeType(command.ShowOne):
help=_('Set a property on this volume type '
'(repeat option to set multiple properties)'),
)
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_("Allow <project> to access private type (name or ID) "
+ "(Must be used with --private option)"),
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
-
+ identity_client = self.app.client_manager.identity
volume_client = self.app.client_manager.volume
+ if parsed_args.project and not parsed_args.private:
+ msg = _("--project is only allowed with --private")
+ raise exceptions.CommandError(msg)
+
kwargs = {}
if parsed_args.public:
kwargs['is_public'] = True
@@ -79,6 +93,20 @@ class CreateVolumeType(command.ShowOne):
**kwargs
)
volume_type._info.pop('extra_specs')
+
+ if parsed_args.project:
+ try:
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ volume_client.volume_type_access.add_project_access(
+ volume_type.id, project_id)
+ except Exception as e:
+ msg = _("Failed to add project %(project)s access to "
+ "type: %(e)s")
+ LOG.error(msg % {'project': parsed_args.project, 'e': e})
if parsed_args.property:
result = volume_type.set_keys(parsed_args.property)
volume_type._info.update({'properties': utils.format_dict(result)})
@@ -87,22 +115,39 @@ class CreateVolumeType(command.ShowOne):
class DeleteVolumeType(command.Command):
- """Delete volume type"""
+ """Delete volume type(s)"""
def get_parser(self, prog_name):
parser = super(DeleteVolumeType, self).get_parser(prog_name)
parser.add_argument(
- "volume_type",
+ "volume_types",
metavar="<volume-type>",
- help=_("Volume type to delete (name or ID)")
+ nargs="+",
+ help=_("Volume type(s) to delete (name or ID)")
)
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
- volume_type = utils.find_resource(
- volume_client.volume_types, parsed_args.volume_type)
- volume_client.volume_types.delete(volume_type.id)
+ result = 0
+
+ for volume_type in parsed_args.volume_types:
+ try:
+ vol_type = utils.find_resource(volume_client.volume_types,
+ volume_type)
+
+ volume_client.volume_types.delete(vol_type)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete volume type with "
+ "name or ID '%(volume_type)s': %(e)s")
+ % {'volume_type': volume_type, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.volume_types)
+ msg = (_("%(result)s of %(total)s volume types failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListVolumeType(command.Lister):
@@ -115,6 +160,17 @@ class ListVolumeType(command.Lister):
action='store_true',
default=False,
help=_('List additional fields in output'))
+ public_group = parser.add_mutually_exclusive_group()
+ public_group.add_argument(
+ "--public",
+ action="store_true",
+ help=_("List only public types")
+ )
+ public_group.add_argument(
+ "--private",
+ action="store_true",
+ help=_("List only private types (admin only)")
+ )
return parser
def take_action(self, parsed_args):
@@ -124,7 +180,14 @@ class ListVolumeType(command.Lister):
else:
columns = ['ID', 'Name']
column_headers = columns
- data = self.app.client_manager.volume.volume_types.list()
+
+ is_public = None
+ if parsed_args.public:
+ is_public = True
+ if parsed_args.private:
+ is_public = False
+ data = self.app.client_manager.volume.volume_types.list(
+ is_public=is_public)
return (column_headers,
(utils.get_item_properties(
s, columns,
@@ -190,16 +253,15 @@ class SetVolumeType(command.Command):
**kwargs
)
except Exception as e:
- self.app.log.error(_("Failed to update volume type name or"
- " description: %s") % str(e))
+ LOG.error(_("Failed to update volume type name or"
+ " description: %s"), e)
result += 1
if parsed_args.property:
try:
volume_type.set_keys(parsed_args.property)
except Exception as e:
- self.app.log.error(_("Failed to set volume type"
- " property: %s") % str(e))
+ LOG.error(_("Failed to set volume type property: %s"), e)
result += 1
if parsed_args.project:
@@ -213,13 +275,13 @@ class SetVolumeType(command.Command):
volume_client.volume_type_access.add_project_access(
volume_type.id, project_info.id)
except Exception as e:
- self.app.log.error(_("Failed to set volume type access to"
- " project: %s") % str(e))
+ LOG.error(_("Failed to set volume type access to "
+ "project: %s"), e)
result += 1
if result > 0:
raise exceptions.CommandError(_("Command Failed: One or more of"
- " the operations failed"))
+ " the operations failed"))
class ShowVolumeType(command.ShowOne):
@@ -238,8 +300,24 @@ class ShowVolumeType(command.ShowOne):
volume_client = self.app.client_manager.volume
volume_type = utils.find_resource(
volume_client.volume_types, parsed_args.volume_type)
- properties = utils.format_dict(volume_type._info.pop('extra_specs'))
+ properties = utils.format_dict(
+ volume_type._info.pop('extra_specs', {}))
volume_type._info.update({'properties': properties})
+ access_project_ids = None
+ if not volume_type.is_public:
+ try:
+ volume_type_access = volume_client.volume_type_access.list(
+ volume_type.id)
+ project_ids = [utils.get_field(item, 'project_id')
+ for item in volume_type_access]
+ # TODO(Rui Chen): This format list case can be removed after
+ # patch https://review.openstack.org/#/c/330223/ merged.
+ access_project_ids = utils.format_list(project_ids)
+ except Exception as e:
+ msg = _('Failed to get access project list for volume type '
+ '%(type)s: %(e)s')
+ LOG.error(msg % {'type': volume_type.id, 'e': e})
+ volume_type._info.update({'access_project_ids': access_project_ids})
return zip(*sorted(six.iteritems(volume_type._info)))
@@ -284,8 +362,7 @@ class UnsetVolumeType(command.Command):
try:
volume_type.unset_keys(parsed_args.property)
except Exception as e:
- self.app.log.error(_("Failed to unset volume type property: %s"
- ) % str(e))
+ LOG.error(_("Failed to unset volume type property: %s"), e)
result += 1
if parsed_args.project:
@@ -299,8 +376,8 @@ class UnsetVolumeType(command.Command):
volume_client.volume_type_access.remove_project_access(
volume_type.id, project_info.id)
except Exception as e:
- self.app.log.error(_("Failed to remove volume type access from"
- " project: %s") % str(e))
+ LOG.error(_("Failed to remove volume type access from "
+ "project: %s"), e)
result += 1
if result > 0:
diff --git a/releasenotes/notes/add-port-unset-command-8bdaf1fa9c593374.yaml b/releasenotes/notes/add-port-unset-command-8bdaf1fa9c593374.yaml
new file mode 100644
index 0000000..f4e547b
--- /dev/null
+++ b/releasenotes/notes/add-port-unset-command-8bdaf1fa9c593374.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add a new command ``port unset`` to clear the information
+ of fixed-ip and binding-profile from the port.
+ [ Blueprint `network-property-unset <https://blueprints.launchpad.net/python-openstackclient/+spec/network-property-unset>`_]
diff --git a/releasenotes/notes/add-server-group-quotas-b67fcba98619f0c9.yaml b/releasenotes/notes/add-server-group-quotas-b67fcba98619f0c9.yaml
new file mode 100644
index 0000000..9b62b3e
--- /dev/null
+++ b/releasenotes/notes/add-server-group-quotas-b67fcba98619f0c9.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - Added support of --server-groups --server-group-members options
+ to ``quota set`` command.
+
+ [Bug `1602223 <https://bugs.launchpad.net/python-openstackclient/+bug/1602223>`_]
diff --git a/releasenotes/notes/bp-backup-snapshot-renamed-for-volume-resource-2d2d13ea8489a61f.yaml b/releasenotes/notes/bp-backup-snapshot-renamed-for-volume-resource-2d2d13ea8489a61f.yaml
new file mode 100644
index 0000000..c985140
--- /dev/null
+++ b/releasenotes/notes/bp-backup-snapshot-renamed-for-volume-resource-2d2d13ea8489a61f.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - Add new commands ``volume backup create/delete/list/show/restore``. It is
+ used to replace the old commands ``backup create/delete/list/show/restore``.
+ [Blueprint `backup-snapshot-renamed-for-volume-resource <https://blueprints.launchpad.net/python-openstackclient/+spec/backup-snapshot-renamed-for-volume-resource>`_]
+deprecations:
+ - Deprecate commands ``backup create/delete/list/show/restore``.
+ [Blueprint `backup-snapshot-renamed-for-volume-resource <https://blueprints.launchpad.net/python-openstackclient/+spec/backup-snapshot-renamed-for-volume-resource>`_]
diff --git a/releasenotes/notes/bp-implement-network-agents-5eba48796318f094.yaml b/releasenotes/notes/bp-implement-network-agents-5eba48796318f094.yaml
new file mode 100644
index 0000000..d9224c3
--- /dev/null
+++ b/releasenotes/notes/bp-implement-network-agents-5eba48796318f094.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - Add ``network agent delete``, ``network agent list``, ``network agent show``
+ and ``network agent set`` commands.
+ [Blueprint `implement-network-agents <https://blueprints.launchpad.net/python-openstackclient/+spec/implement-network-agents>`_]
diff --git a/releasenotes/notes/bp-multi-argument-compute-0bc4522f6edca355.yaml b/releasenotes/notes/bp-multi-argument-compute-0bc4522f6edca355.yaml
new file mode 100644
index 0000000..2e129df
--- /dev/null
+++ b/releasenotes/notes/bp-multi-argument-compute-0bc4522f6edca355.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - Support bulk deletion and error handling for ``aggregate delete``,
+ ``flavor delete``, ``keypair delete`` and ``service delete`` commands.
+ [Blueprint `multi-argument-compute <https://blueprints.launchpad.net/python-openstackclient/+spec/multi-argument-compute>`_]
diff --git a/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml b/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml
new file mode 100644
index 0000000..9bfc86a
--- /dev/null
+++ b/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - Support bulk deletion for ``subnet pool delete``, ``subnet delete``,
+ ``floating ip delete``, ``security group delete`` and
+ ``security group rule delete``.
+ [Blueprint `multi-argument-network <https://blueprints.launchpad.net/python-openstackclient/+spec/multi-argument-network>`_]
diff --git a/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml b/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml
new file mode 100644
index 0000000..74ace6c
--- /dev/null
+++ b/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml
@@ -0,0 +1,17 @@
+---
+features:
+ - Update ``--binding-profile`` option on the ``port create`` and
+ ``port set`` commands to support JSON input for more advanced
+ binding profile data.
+ [Blueprint :oscbp:`neutron-client`]
+ - Add ``--enable-port-security`` and ``--disable-port-security``
+ options on the ``network create`` and ``network set`` commands.
+ This supports setting the default port security for ports created
+ on a network.
+ [Blueprint :oscbp:`neutron-client`]
+ - Add ``geneve`` choice to the ``network create`` command
+ ``--provider-network-type`` option.
+ [Blueprint :oscbp:`neutron-client`]
+ - Add ``--device-owner`` option to the ``port list`` command
+ to enable listing ports based on device owner.
+ [Blueprint :oscbp:`neutron-client`]
diff --git a/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml b/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml
new file mode 100644
index 0000000..eef92c9
--- /dev/null
+++ b/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - Add ``network rbac list``, ``network rbac show``, ``network rbac create``,
+ ``network rbac delete`` and ``network rbac set`` commands.
+ [Blueprint `neutron-client-rbac <https://blueprints.launchpad.net/python-openstackclient/+spec/neutron-client-rbac>`_]
diff --git a/releasenotes/notes/bp-routed-networks-86a24f34d86fca53.yaml b/releasenotes/notes/bp-routed-networks-86a24f34d86fca53.yaml
new file mode 100644
index 0000000..6c4a185
--- /dev/null
+++ b/releasenotes/notes/bp-routed-networks-86a24f34d86fca53.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - Add ``--network-segment`` option to the ``subnet create`` command.
+ This is a beta command option and subject to change. Use global option
+ ``--os-beta-command`` to enable this option.
+ [Blueprint `routed-networks <https://blueprints.launchpad.net/neutron/+spec/routed-networks>`_]
diff --git a/releasenotes/notes/bug-1535213-c91133586e07d71c.yaml b/releasenotes/notes/bug-1535213-c91133586e07d71c.yaml
new file mode 100644
index 0000000..d3f36e3
--- /dev/null
+++ b/releasenotes/notes/bug-1535213-c91133586e07d71c.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ Support a new ``--state`` option for ``volume set`` command that
+ changes the state of a volume.
+ [Bug `1535213 <https://bugs.launchpad.net/bugs/1535213>`_]
diff --git a/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml b/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml
index c783d01..98e037b 100644
--- a/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml
+++ b/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml
@@ -3,4 +3,3 @@ fixes:
- Keystone V3 `user password set` is a self-service operation. It should
not required a scoped token as it is not considered a `scoped operation`.
[Bug `1543222 <https://bugs.launchpad.net/bugs/1543222>`_]
-
diff --git a/releasenotes/notes/bug-1561599-d5f541f08ae6274a.yaml b/releasenotes/notes/bug-1561599-d5f541f08ae6274a.yaml
new file mode 100644
index 0000000..8624a95
--- /dev/null
+++ b/releasenotes/notes/bug-1561599-d5f541f08ae6274a.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - When performing ``domain show``, ``project show`` or ``user show``, peek
+ into the user token to determine the ID or the resource (if supplied with
+ only a name). This should make finding information about the user and
+ their project easier for non-admin users.
+ [Bug `1561599 <https://bugs.launchpad.net/bugs/1561599>`_]
diff --git a/releasenotes/notes/bug-1575461-3fed33e53795684a.yaml b/releasenotes/notes/bug-1575461-3fed33e53795684a.yaml
new file mode 100644
index 0000000..cd31945
--- /dev/null
+++ b/releasenotes/notes/bug-1575461-3fed33e53795684a.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add support for showing flavor access list by using ``flavor show`` command.
+ [Bug `1575461 <https://bugs.launchpad.net/bugs/1575461>`_]
diff --git a/releasenotes/notes/bug-1582774-3bba709ef61e33b7.yaml b/releasenotes/notes/bug-1582774-3bba709ef61e33b7.yaml
new file mode 100644
index 0000000..35c7217
--- /dev/null
+++ b/releasenotes/notes/bug-1582774-3bba709ef61e33b7.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - Fix setting defaults for some scope parameters, that were putting invalid
+ scope parameters for some auth plugins.
+ [Bug `1582774 <https://bugs.launchpad.net/bugs/1582774>`_]
diff --git a/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml b/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml
new file mode 100644
index 0000000..8621a81
--- /dev/null
+++ b/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - All ``set`` and ``unset`` commands now return normally when nothing
+ specified to modify. This will become the default behavior of OSC
+ ``set`` and ``unset`` commands.
+ [Bug `1588588 <https://bugs.launchpad.net/python-openstackclient/+bug/1588588>`_]
diff --git a/releasenotes/notes/bug-1589332-2941f5286df7e5d4.yaml b/releasenotes/notes/bug-1589332-2941f5286df7e5d4.yaml
new file mode 100644
index 0000000..0ac17c3
--- /dev/null
+++ b/releasenotes/notes/bug-1589332-2941f5286df7e5d4.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Add ``--purge`` option to ``volume delete`` command (Volume v2 only) in
+ order to removing any snapshots along with volume automatically when user
+ delete the volume.
+ [Bug `1589332 <https://bugs.launchpad.net/python-openstackclient/+bug/1589332>`_]
diff --git a/releasenotes/notes/bug-1589348-4a612a4efc7ed0e5.yaml b/releasenotes/notes/bug-1589348-4a612a4efc7ed0e5.yaml
new file mode 100644
index 0000000..cee2cbf
--- /dev/null
+++ b/releasenotes/notes/bug-1589348-4a612a4efc7ed0e5.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - Add options ``--up`` and ``--down`` for compute v2 ``compute service set``
+ command to support force up/down compute service.
+ [Bug `1589348 <https://bugs.launchpad.net/python-openstackclient/+bug/1589348>`_]
diff --git a/releasenotes/notes/bug-1589935-8a56e6a18d836db9.yaml b/releasenotes/notes/bug-1589935-8a56e6a18d836db9.yaml
new file mode 100644
index 0000000..53c1ea0
--- /dev/null
+++ b/releasenotes/notes/bug-1589935-8a56e6a18d836db9.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - Raise ``ArgumentTypeError`` if the input arguments do not match the type
+ ``key=value`` when we set properties.
+ [Bug `1589935 <https://bugs.launchpad.net/bugs/1589935>`_]
diff --git a/releasenotes/notes/bug-1592062-e327a31a5ae66809.yaml b/releasenotes/notes/bug-1592062-e327a31a5ae66809.yaml
new file mode 100644
index 0000000..2a7751a
--- /dev/null
+++ b/releasenotes/notes/bug-1592062-e327a31a5ae66809.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - Scope options are now validated after authentication occurs, and only if
+ the user does not have a default project scope.
+ [Bug `1592062 <https://bugs.launchpad.net/bugs/1592062>`_]
diff --git a/releasenotes/notes/bug-1592368-a4af69f163a1e208.yaml b/releasenotes/notes/bug-1592368-a4af69f163a1e208.yaml
new file mode 100644
index 0000000..acea6fa
--- /dev/null
+++ b/releasenotes/notes/bug-1592368-a4af69f163a1e208.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - Fix for network OS_ENDPOINT_TYPE/--os-interface.
+ Previously these were being ignored for network commands
+ which resulted in the public endpoint always being used.
+ [Bug `1592368 <https://bugs.launchpad.net/bugs/1592368>`_]
diff --git a/releasenotes/notes/bug-1592761-f45998453d6801f7.yaml b/releasenotes/notes/bug-1592761-f45998453d6801f7.yaml
new file mode 100644
index 0000000..78f5801
--- /dev/null
+++ b/releasenotes/notes/bug-1592761-f45998453d6801f7.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - Add default IP version in ``ip availability list`` command and make
+ this command work properly without ``--ip-version`` option.
+ [Bug `1592761 <https://bugs.launchpad.net/bugs/1592761>`_]
diff --git a/releasenotes/notes/bug-1592906-a5604ec5abe77507.yaml b/releasenotes/notes/bug-1592906-a5604ec5abe77507.yaml
new file mode 100644
index 0000000..a9b0606
--- /dev/null
+++ b/releasenotes/notes/bug-1592906-a5604ec5abe77507.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - Support bulk deletion for ``ec2 credentials delete``, ``endpoint delete``,
+ ``service delete`` in identity V2.0 .
+ [Bug `1592906 <https://bugs.launchpad.net/bugs/1592906>`_]
diff --git a/releasenotes/notes/bug-1592906-ad67ce8736f3cd48.yaml b/releasenotes/notes/bug-1592906-ad67ce8736f3cd48.yaml
new file mode 100644
index 0000000..e674a20
--- /dev/null
+++ b/releasenotes/notes/bug-1592906-ad67ce8736f3cd48.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ Support bulk deletion for identity v3 commands: ``consumer``,
+ ``credential``, ``domain``, ``ec2creds``, ``endpoint``,
+ ``federation_protocol``, ``identity_provider``, ``mapping``,
+ ``policy``, ``region``, ``service_provider`` and ``service``.
+ [Bug `1592906 <https://bugs.launchpad.net/bugs/1592906>`_]
diff --git a/releasenotes/notes/bug-1592906-e69b37377278d9c2.yaml b/releasenotes/notes/bug-1592906-e69b37377278d9c2.yaml
new file mode 100644
index 0000000..fee971d
--- /dev/null
+++ b/releasenotes/notes/bug-1592906-e69b37377278d9c2.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - Support bulk deletion for ``volume type delete``.
+ [Bug `1592906 <https://bugs.launchpad.net/bugs/1592906>`_]
+
diff --git a/releasenotes/notes/bug-1596443-9e2af267e91d1643.yaml b/releasenotes/notes/bug-1596443-9e2af267e91d1643.yaml
new file mode 100644
index 0000000..cf4e85c
--- /dev/null
+++ b/releasenotes/notes/bug-1596443-9e2af267e91d1643.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add ``--force`` option to ``backup create`` command to allow users to
+ back up an in-use volume.
+ [Bug `1596443 <https://bugs.launchpad.net/bugs/1596443>`_]
diff --git a/releasenotes/notes/bug-1596798-b22fd587bdca8b36.yaml b/releasenotes/notes/bug-1596798-b22fd587bdca8b36.yaml
new file mode 100644
index 0000000..6ab833d
--- /dev/null
+++ b/releasenotes/notes/bug-1596798-b22fd587bdca8b36.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - Add ``--property`` option to ``flavor create`` command.
+ [Bug `1596798 <https://bugs.launchpad.net/bugs/1596798>`_]
diff --git a/releasenotes/notes/bug-1596821-a07599eb4beb6342.yaml b/releasenotes/notes/bug-1596821-a07599eb4beb6342.yaml
new file mode 100644
index 0000000..4843314
--- /dev/null
+++ b/releasenotes/notes/bug-1596821-a07599eb4beb6342.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add ``--force`` option to ``volume qos delete`` command to allow users to
+ delete in-use QoS specification(s).
+ [Bug `1596821 <https://bugs.launchpad.net/bugs/1596821>`_]
diff --git a/releasenotes/notes/bug-1597184-f4fb687b3d4d99d9.yaml b/releasenotes/notes/bug-1597184-f4fb687b3d4d99d9.yaml
new file mode 100644
index 0000000..638afb0
--- /dev/null
+++ b/releasenotes/notes/bug-1597184-f4fb687b3d4d99d9.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add ``--snapshot`` option to ``backup create`` command.
+ [Bug `1597184 <https://bugs.launchpad.net/bugs/1597184>`_]
diff --git a/releasenotes/notes/bug-1597188-a2ff62b809865365.yaml b/releasenotes/notes/bug-1597188-a2ff62b809865365.yaml
new file mode 100644
index 0000000..d1f23c4
--- /dev/null
+++ b/releasenotes/notes/bug-1597188-a2ff62b809865365.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add ``--force`` option to ``backup delete`` command to allow delete
+ in state other than error or available.
+ [Bug `1597188 <https://bugs.launchpad.net/bugs/1597188>`_]
diff --git a/releasenotes/notes/bug-1597192-52801f7520287309.yaml b/releasenotes/notes/bug-1597192-52801f7520287309.yaml
new file mode 100644
index 0000000..2069ca2
--- /dev/null
+++ b/releasenotes/notes/bug-1597192-52801f7520287309.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - Add ``--property`` option to ``snapshot create`` command.
+ [Bug `1597192 <https://bugs.launchpad.net/bugs/1597192>`_]
diff --git a/releasenotes/notes/bug-1597198-e36b55f3fd185a3a.yaml b/releasenotes/notes/bug-1597198-e36b55f3fd185a3a.yaml
new file mode 100644
index 0000000..a08caac
--- /dev/null
+++ b/releasenotes/notes/bug-1597198-e36b55f3fd185a3a.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add ``--public`` and ``--private`` options to ``volume type list`` command.
+ [Bug `1597198 <https://bugs.launchpad.net/bugs/1597198>`_]
diff --git a/releasenotes/notes/bug-1597296-9735f33eacf5552e.yaml b/releasenotes/notes/bug-1597296-9735f33eacf5552e.yaml
new file mode 100644
index 0000000..677f8d4
--- /dev/null
+++ b/releasenotes/notes/bug-1597296-9735f33eacf5552e.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - Fixed service name lookup in Identity commands to properly handle
+ multiple matches.
+ [Bug `1597296 <https://bugs.launchpad.net/python-openstackclient/+bug/1597296>`_]
diff --git a/releasenotes/notes/bug-1600196-6a733dd4e3371df7.yaml b/releasenotes/notes/bug-1600196-6a733dd4e3371df7.yaml
new file mode 100644
index 0000000..16c125f
--- /dev/null
+++ b/releasenotes/notes/bug-1600196-6a733dd4e3371df7.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add ``--incremental`` option to ``backup create`` command to allow users to
+ create incremental backups.
+ [Bug `1600196 <https://bugs.launchpad.net/python-openstackclient/+bug/1600196>`_]
diff --git a/releasenotes/notes/bug-1602169-2750c9a6896d2825.yaml b/releasenotes/notes/bug-1602169-2750c9a6896d2825.yaml
new file mode 100644
index 0000000..1ccd68f
--- /dev/null
+++ b/releasenotes/notes/bug-1602169-2750c9a6896d2825.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - Add ``--project`` and ``--project-domain`` options to ``volume type create``
+ command.
+ [Bug `1602169 <https://bugs.launchpad.net/bugs/1602169>`_]
diff --git a/releasenotes/notes/bug-1605088-fea9347336764469.yaml b/releasenotes/notes/bug-1605088-fea9347336764469.yaml
new file mode 100644
index 0000000..15e0bc4
--- /dev/null
+++ b/releasenotes/notes/bug-1605088-fea9347336764469.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - Support to get server ``rdp``, ``serial``, ``mks`` type console url.
+ [Bug `1605088 <https://bugs.launchpad.net/python-openstackclient/+bug/1605088>`_]
diff --git a/releasenotes/notes/bug-1605475-84e649fb8c675737.yaml b/releasenotes/notes/bug-1605475-84e649fb8c675737.yaml
new file mode 100644
index 0000000..f9362bc
--- /dev/null
+++ b/releasenotes/notes/bug-1605475-84e649fb8c675737.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add ``--limit`` and ``--marker`` options to ``snapshot list`` command.
+ [Bug `1605475 <https://bugs.launchpad.net/bugs/1605475>`_]
diff --git a/releasenotes/notes/bug-1605774-28aec51f6ec4926e.yaml b/releasenotes/notes/bug-1605774-28aec51f6ec4926e.yaml
new file mode 100644
index 0000000..8c7958f
--- /dev/null
+++ b/releasenotes/notes/bug-1605774-28aec51f6ec4926e.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - Deprecate ``role list`` arguments in favor of ``role assignment`` command.
+ [Bug `1605774 <https://bugs.launchpad.net/python-openstackclient/+bug/1605774>`_]
diff --git a/releasenotes/notes/bug-1606105-ca06b230e22ab5c6.yaml b/releasenotes/notes/bug-1606105-ca06b230e22ab5c6.yaml
new file mode 100644
index 0000000..a2c3ac8
--- /dev/null
+++ b/releasenotes/notes/bug-1606105-ca06b230e22ab5c6.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - Add support for domain specific roles in ``role`` and``role assignment`` commands.
+ [Bug `1606105 <https://bugs.launchpad.net/python-openstackclient/+bug/1606105>`_]
diff --git a/releasenotes/notes/bug-1610883-e6345c32a35cc290.yaml b/releasenotes/notes/bug-1610883-e6345c32a35cc290.yaml
new file mode 100644
index 0000000..710ee39
--- /dev/null
+++ b/releasenotes/notes/bug-1610883-e6345c32a35cc290.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Make ``subnet list`` command supports listing up subnets with
+ dhcp enabled/disabled by adding ``--dhcp`` and ``--no-dhcp``
+ options to the command.
+ [Bug `1610883 <https://bugs.launchpad.net/bugs/1610883>`_]
diff --git a/releasenotes/notes/bug-1617384-55c88207115e2a5b.yaml b/releasenotes/notes/bug-1617384-55c88207115e2a5b.yaml
new file mode 100644
index 0000000..89b452b
--- /dev/null
+++ b/releasenotes/notes/bug-1617384-55c88207115e2a5b.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - |
+ Fix prompting for password issue introduced in release 3.0.0
+ [Bug `1617384 <https://bugs.launchpad.net/bugs/1617384>`_]
diff --git a/releasenotes/notes/bug_1602073-5deb58deeafbc8be.yaml b/releasenotes/notes/bug_1602073-5deb58deeafbc8be.yaml
new file mode 100644
index 0000000..be95966
--- /dev/null
+++ b/releasenotes/notes/bug_1602073-5deb58deeafbc8be.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - Add "Checksum" column to output of "image list --long"
+ [Bug `1602073 <https://bugs.launchpad.net/bugs/1602073>`_]
diff --git a/releasenotes/notes/flavor-create-with-project-19d41bfa93e3c6d0.yaml b/releasenotes/notes/flavor-create-with-project-19d41bfa93e3c6d0.yaml
new file mode 100644
index 0000000..312dece
--- /dev/null
+++ b/releasenotes/notes/flavor-create-with-project-19d41bfa93e3c6d0.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - Add ``--project`` and ``--project-domain`` options to the ``flavor create``
+ command. We can use these options to add the flavor access to a given project
+ when we create the flavor.
diff --git a/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml b/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml
new file mode 100644
index 0000000..c5b7f22
--- /dev/null
+++ b/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml
@@ -0,0 +1,24 @@
+---
+features:
+ - Add new command ``floating ip pool list`` to list up all floating ip
+ pools. This command is used to replace the old command
+ ``ip floating pool list``.
+ [Blueprint rework-ip-commands `<https://blueprints.launchpad.net/python-openstackclient/+spec/rework-ip-commands>`_]
+ - Add new commands ``server add/remove floating ip``. They are used to
+ replace the old commands ``ip floating add/remove``.
+ [Blueprint rework-ip-commands `<https://blueprints.launchpad.net/python-openstackclient/+spec/rework-ip-commands>`_]
+ - Add new commands ``server add/remove fixed ip``. They are used to
+ replace the old commands ``ip fixed add/remove``.
+ [Blueprint rework-ip-commands `<https://blueprints.launchpad.net/python-openstackclient/+spec/rework-ip-commands>`_]
+ - Add new commands ``floating ip create/delete/list/show``. It is used to
+ replace the old commands ``ip floating create/delete/list/show``.
+ [Blueprint rework-ip-commands `<https://blueprints.launchpad.net/python-openstackclient/+spec/rework-ip-commands>`_]
+deprecations:
+ - Deprecate command ``ip floating pool list``.
+ [Blueprint rework-ip-commands `<https://blueprints.launchpad.net/python-openstackclient/+spec/rework-ip-commands>`_]
+ - Deprecate commands ``ip floating add/remove``.
+ [Blueprint rework-ip-commands `<https://blueprints.launchpad.net/python-openstackclient/+spec/rework-ip-commands>`_]
+ - Deprecate commands ``ip fixed add/remove``.
+ [Blueprint rework-ip-commands `<https://blueprints.launchpad.net/python-openstackclient/+spec/rework-ip-commands>`_]
+ - Deprecate commands ``ip floating create/delete/list/show``.
+ [Blueprint rework-ip-commands `<https://blueprints.launchpad.net/python-openstackclient/+spec/rework-ip-commands>`_]
diff --git a/releasenotes/notes/list_volume_type_access-f7d9aa6159f757ea.yaml b/releasenotes/notes/list_volume_type_access-f7d9aa6159f757ea.yaml
new file mode 100644
index 0000000..aba3f75
--- /dev/null
+++ b/releasenotes/notes/list_volume_type_access-f7d9aa6159f757ea.yaml
@@ -0,0 +1,10 @@
+---
+features:
+ - |
+ Show project access details for private volume type.
+
+ An user can list projects which have access to
+ a specific private volume type by using
+ ``volume type show <volume-type>``
+
+ [Bug `1554891 <https://bugs.launchpad.net/python-openstackclient/+bug/1554891>`_]
diff --git a/releasenotes/notes/modify-compute-agent-set-77ff894ef62ebbc7.yaml b/releasenotes/notes/modify-compute-agent-set-77ff894ef62ebbc7.yaml
new file mode 100644
index 0000000..28ab362
--- /dev/null
+++ b/releasenotes/notes/modify-compute-agent-set-77ff894ef62ebbc7.yaml
@@ -0,0 +1,3 @@
+---
+upgrade:
+ - Migrate command ``compute agent set`` arguments to be optional.
diff --git a/releasenotes/notes/remove-osc_password-0767ac78267ef114.yaml b/releasenotes/notes/remove-osc_password-0767ac78267ef114.yaml
new file mode 100644
index 0000000..5d288be
--- /dev/null
+++ b/releasenotes/notes/remove-osc_password-0767ac78267ef114.yaml
@@ -0,0 +1,7 @@
+---
+upgrade:
+ - With the change to use keystoneauth plugins the OpenStackClient-specific
+ ``osc_password`` authentication plugin has been removed. The visible
+ difference should only be in the behaviour with poorly configured clouds
+ with old default Keystone values for admin_endpoint and public_endpoint
+ as seen in the version details returned in a GET to the root ('/') route.
diff --git a/releasenotes/notes/subnet-unset-5b458cdbaf93d766.yaml b/releasenotes/notes/subnet-unset-5b458cdbaf93d766.yaml
new file mode 100644
index 0000000..9cb4bdc
--- /dev/null
+++ b/releasenotes/notes/subnet-unset-5b458cdbaf93d766.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add a new command ``subnet unset`` to clear the information
+ of allocation-pools, host-routes or DNS servers from the subnet.
+ [ Blueprint `network-property-unset <https://blueprints.launchpad.net/python-openstackclient/+spec/network-property-unset>`_]
diff --git a/releasenotes/notes/unset-router-7b0cbd9518bb1de6.yaml b/releasenotes/notes/unset-router-7b0cbd9518bb1de6.yaml
new file mode 100644
index 0000000..6811c84
--- /dev/null
+++ b/releasenotes/notes/unset-router-7b0cbd9518bb1de6.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Add a new command ``router unset`` to clear the information
+ of routes from the router.
+ [ Blueprint `network-property-unset <https://blueprints.launchpad.net/python-openstackclient/+spec/network-property-unset>`_]
+
diff --git a/releasenotes/notes/unset-subnet-pool-333052dd85b95653.yaml b/releasenotes/notes/unset-subnet-pool-333052dd85b95653.yaml
new file mode 100644
index 0000000..7fbda24
--- /dev/null
+++ b/releasenotes/notes/unset-subnet-pool-333052dd85b95653.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add a new command ``subnet pool unset`` to clear the information
+ of pool-prefixes from the subnet pools.
+ [ Blueprint `network-property-unset <https://blueprints.launchpad.net/python-openstackclient/+spec/network-property-unset>`_]
diff --git a/releasenotes/source/20_releases.rst b/releasenotes/source/20_releases.rst
new file mode 100644
index 0000000..ca2508f
--- /dev/null
+++ b/releasenotes/source/20_releases.rst
@@ -0,0 +1,6 @@
+========================
+2.0 Series Release Notes
+========================
+
+.. release-notes::
+ :version: 2.6.0,2.5.0,2.4.0,2.2.0,2.1.0,2.0.0
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index 12ed68d..d765e33 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -285,7 +285,7 @@ texinfo_documents = [(
u'OpenStackclient Release Notes Documentation',
u'OpenStackclient Developers',
'OpenStackClientReleaseNotes',
- 'One line description of project.',
+ 'A unified command-line client for OpenStack.',
'Miscellaneous',
)]
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index e0477df..4004e35 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,5 +6,28 @@ OpenStackClient Release Notes
:maxdepth: 1
unreleased
- mitaka
+ 20_releases
pre_20_releases
+
+OpenStack Releases
+------------------
+
+OpenStackClient is compatible with all currently supported OpenStack releases,
+it does not require maintaining a 'Mitaka' version to match to a Mitala-release
+cloud. The OpenStackClient release that was current when the corresponding
+OpenStack release was made is shown below:
+
+================= =======================
+OpenStack Release OpenStackClient Release
+================= =======================
+Mitaka 2.3.0
+Liberty 1.7.3
+Kilo 1.0.6
+Juno 0.4.1
+Icehouse 0.3.1
+================= =======================
+
+Further details for historical OpenStack releases are found at the
+`OpenStack Releases`_ page.
+
+.. _`OpenStack Releases`: http://releases.openstack.org/
diff --git a/releasenotes/source/mitaka.rst b/releasenotes/source/mitaka.rst
deleted file mode 100644
index 05ed3e4..0000000
--- a/releasenotes/source/mitaka.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-===========================
-Mitaka Series Release Notes
-===========================
-
-.. release-notes::
- :branch: origin/stable/mitaka
diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst
index cb3cccf..62d0c01 100644
--- a/releasenotes/source/unreleased.rst
+++ b/releasenotes/source/unreleased.rst
@@ -3,3 +3,4 @@ Current Release Notes
=====================
.. release-notes::
+ :earliest-version: 3.0
diff --git a/requirements.txt b/requirements.txt
index 5d8c844..f065346 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,15 +6,12 @@ six>=1.9.0 # MIT
Babel>=2.3.4 # BSD
cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0
-keystoneauth1>=2.1.0 # Apache-2.0
-openstacksdk>=0.8.6 # Apache-2.0
-os-client-config>=1.13.1 # Apache-2.0
-oslo.config>=3.10.0 # Apache-2.0
+keystoneauth1>=2.10.0 # Apache-2.0
+openstacksdk>=0.9.4 # Apache-2.0
+osc-lib>=1.0.2 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
-oslo.utils>=3.11.0 # Apache-2.0
-python-glanceclient>=2.0.0 # Apache-2.0
-python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0
+oslo.utils>=3.16.0 # Apache-2.0
+python-glanceclient!=2.4.0,>=2.3.0 # Apache-2.0
+python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0
-requests>=2.10.0 # Apache-2.0
-stevedore>=1.10.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 2bff76b..78eaf34 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -17,6 +17,7 @@ classifier =
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
+ Programming Language :: Python :: 3.5
[files]
packages =
@@ -26,9 +27,8 @@ packages =
console_scripts =
openstack = openstackclient.shell:main
-keystoneclient.auth.plugin =
+keystoneauth1.plugin =
token_endpoint = openstackclient.api.auth_plugin:TokenEndpoint
- osc_password = openstackclient.api.auth_plugin:OSCGenericPassword
openstack.cli =
command_list = openstackclient.common.module:ListCommand
@@ -93,24 +93,26 @@ openstack.compute.v2 =
ip_floating_add = openstackclient.compute.v2.floatingip:AddFloatingIP
ip_floating_remove = openstackclient.compute.v2.floatingip:RemoveFloatingIP
- ip_floating_pool_list = openstackclient.compute.v2.floatingippool:ListFloatingIPPool
keypair_create = openstackclient.compute.v2.keypair:CreateKeypair
keypair_delete = openstackclient.compute.v2.keypair:DeleteKeypair
keypair_list = openstackclient.compute.v2.keypair:ListKeypair
keypair_show = openstackclient.compute.v2.keypair:ShowKeypair
+ server_add_fixed_ip = openstackclient.compute.v2.server:AddFixedIP
+ server_add_floating_ip = openstackclient.compute.v2.server:AddFloatingIP
server_add_security_group = openstackclient.compute.v2.server:AddServerSecurityGroup
server_add_volume = openstackclient.compute.v2.server:AddServerVolume
server_create = openstackclient.compute.v2.server:CreateServer
server_delete = openstackclient.compute.v2.server:DeleteServer
- server_image_create = openstackclient.compute.v2.server:CreateServerImage
server_list = openstackclient.compute.v2.server:ListServer
server_lock = openstackclient.compute.v2.server:LockServer
server_migrate = openstackclient.compute.v2.server:MigrateServer
server_pause = openstackclient.compute.v2.server:PauseServer
server_reboot = openstackclient.compute.v2.server:RebootServer
server_rebuild = openstackclient.compute.v2.server:RebuildServer
+ server_remove_fixed_ip = openstackclient.compute.v2.server:RemoveFixedIP
+ server_remove_floating_ip = openstackclient.compute.v2.server:RemoveFloatingIP
server_remove_security_group = openstackclient.compute.v2.server:RemoveServerSecurityGroup
server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume
server_rescue = openstackclient.compute.v2.server:RescueServer
@@ -138,6 +140,8 @@ openstack.compute.v2 =
server_group_list = openstackclient.compute.v2.server_group:ListServerGroup
server_group_show = openstackclient.compute.v2.server_group:ShowServerGroup
+ server_image_create = openstackclient.compute.v2.server_image:CreateServerImage
+
usage_list = openstackclient.compute.v2.usage:ListUsage
usage_show = openstackclient.compute.v2.usage:ShowUsage
@@ -168,6 +172,7 @@ openstack.identity.v2 =
role_list = openstackclient.identity.v2_0.role:ListRole
role_remove = openstackclient.identity.v2_0.role:RemoveRole
role_show = openstackclient.identity.v2_0.role:ShowRole
+ role_assignment_list = openstackclient.identity.v2_0.role_assignment:ListRoleAssignment
service_create = openstackclient.identity.v2_0.service:CreateService
service_delete = openstackclient.identity.v2_0.service:DeleteService
@@ -333,13 +338,27 @@ openstack.network.v2 =
address_scope_set = openstackclient.network.v2.address_scope:SetAddressScope
address_scope_show = openstackclient.network.v2.address_scope:ShowAddressScope
+ floating_ip_create = openstackclient.network.v2.floating_ip:CreateFloatingIP
+ floating_ip_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP
+ floating_ip_list = openstackclient.network.v2.floating_ip:ListFloatingIP
+ floating_ip_show = openstackclient.network.v2.floating_ip:ShowFloatingIP
+
+ floating_ip_pool_list = openstackclient.network.v2.floating_ip_pool:ListFloatingIPPool
+
ip_availability_list = openstackclient.network.v2.ip_availability:ListIPAvailability
ip_availability_show = openstackclient.network.v2.ip_availability:ShowIPAvailability
- ip_floating_create = openstackclient.network.v2.floating_ip:CreateFloatingIP
- ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP
- ip_floating_list = openstackclient.network.v2.floating_ip:ListFloatingIP
- ip_floating_show = openstackclient.network.v2.floating_ip:ShowFloatingIP
+ ip_floating_create = openstackclient.network.v2.floating_ip:CreateIPFloating
+ ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteIPFloating
+ ip_floating_list = openstackclient.network.v2.floating_ip:ListIPFloating
+ ip_floating_show = openstackclient.network.v2.floating_ip:ShowIPFloating
+
+ ip_floating_pool_list = openstackclient.network.v2.floating_ip_pool:ListIPFloatingPool
+
+ network_agent_delete = openstackclient.network.v2.network_agent:DeleteNetworkAgent
+ network_agent_list = openstackclient.network.v2.network_agent:ListNetworkAgent
+ network_agent_set = openstackclient.network.v2.network_agent:SetNetworkAgent
+ network_agent_show = openstackclient.network.v2.network_agent:ShowNetworkAgent
network_create = openstackclient.network.v2.network:CreateNetwork
network_delete = openstackclient.network.v2.network:DeleteNetwork
@@ -347,6 +366,12 @@ openstack.network.v2 =
network_set = openstackclient.network.v2.network:SetNetwork
network_show = openstackclient.network.v2.network:ShowNetwork
+ network_rbac_create = openstackclient.network.v2.network_rbac:CreateNetworkRBAC
+ network_rbac_delete = openstackclient.network.v2.network_rbac:DeleteNetworkRBAC
+ network_rbac_list = openstackclient.network.v2.network_rbac:ListNetworkRBAC
+ network_rbac_set = openstackclient.network.v2.network_rbac:SetNetworkRBAC
+ network_rbac_show = openstackclient.network.v2.network_rbac:ShowNetworkRBAC
+
network_segment_list = openstackclient.network.v2.network_segment:ListNetworkSegment
network_segment_show = openstackclient.network.v2.network_segment:ShowNetworkSegment
@@ -355,6 +380,7 @@ openstack.network.v2 =
port_list = openstackclient.network.v2.port:ListPort
port_set = openstackclient.network.v2.port:SetPort
port_show = openstackclient.network.v2.port:ShowPort
+ port_unset = openstackclient.network.v2.port:UnsetPort
router_add_port = openstackclient.network.v2.router:AddPortToRouter
router_add_subnet = openstackclient.network.v2.router:AddSubnetToRouter
@@ -365,6 +391,7 @@ openstack.network.v2 =
router_remove_subnet = openstackclient.network.v2.router:RemoveSubnetFromRouter
router_set = openstackclient.network.v2.router:SetRouter
router_show = openstackclient.network.v2.router:ShowRouter
+ router_unset = openstackclient.network.v2.router:UnsetRouter
security_group_create = openstackclient.network.v2.security_group:CreateSecurityGroup
security_group_delete = openstackclient.network.v2.security_group:DeleteSecurityGroup
@@ -382,12 +409,14 @@ openstack.network.v2 =
subnet_list = openstackclient.network.v2.subnet:ListSubnet
subnet_set = openstackclient.network.v2.subnet:SetSubnet
subnet_show = openstackclient.network.v2.subnet:ShowSubnet
+ subnet_unset = openstackclient.network.v2.subnet:UnsetSubnet
subnet_pool_create = openstackclient.network.v2.subnet_pool:CreateSubnetPool
subnet_pool_delete = openstackclient.network.v2.subnet_pool:DeleteSubnetPool
subnet_pool_list = openstackclient.network.v2.subnet_pool:ListSubnetPool
subnet_pool_set = openstackclient.network.v2.subnet_pool:SetSubnetPool
subnet_pool_show = openstackclient.network.v2.subnet_pool:ShowSubnetPool
+ subnet_pool_unset = openstackclient.network.v2.subnet_pool:UnsetSubnetPool
openstack.object_store.v1 =
object_store_account_set = openstackclient.object.v1.account:SetAccount
@@ -429,6 +458,12 @@ openstack.volume.v1 =
volume_show = openstackclient.volume.v1.volume:ShowVolume
volume_unset = openstackclient.volume.v1.volume:UnsetVolume
+ volume_backup_create = openstackclient.volume.v1.backup:CreateVolumeBackup
+ volume_backup_delete = openstackclient.volume.v1.backup:DeleteVolumeBackup
+ volume_backup_list = openstackclient.volume.v1.backup:ListVolumeBackup
+ volume_backup_restore = openstackclient.volume.v1.backup:RestoreVolumeBackup
+ volume_backup_show = openstackclient.volume.v1.backup:ShowVolumeBackup
+
volume_type_create = openstackclient.volume.v1.volume_type:CreateVolumeType
volume_type_delete = openstackclient.volume.v1.volume_type:DeleteVolumeType
volume_type_list = openstackclient.volume.v1.volume_type:ListVolumeType
@@ -470,6 +505,12 @@ openstack.volume.v2 =
volume_show = openstackclient.volume.v2.volume:ShowVolume
volume_unset = openstackclient.volume.v2.volume:UnsetVolume
+ volume_backup_create = openstackclient.volume.v2.backup:CreateVolumeBackup
+ volume_backup_delete = openstackclient.volume.v2.backup:DeleteVolumeBackup
+ volume_backup_list = openstackclient.volume.v2.backup:ListVolumeBackup
+ volume_backup_restore = openstackclient.volume.v2.backup:RestoreVolumeBackup
+ volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup
+
volume_type_create = openstackclient.volume.v2.volume_type:CreateVolumeType
volume_type_delete = openstackclient.volume.v2.volume_type:DeleteVolumeType
volume_type_list = openstackclient.volume.v2.volume_type:ListVolumeType
diff --git a/test-requirements.txt b/test-requirements.txt
index a8d6aa4..0ff0e22 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -4,31 +4,36 @@
hacking<0.11,>=0.10.0
coverage>=3.6 # Apache-2.0
-discover # BSD
fixtures>=3.0.0 # Apache-2.0/BSD
mock>=2.0 # BSD
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
-reno>=1.6.2 # Apache2
-requests-mock>=0.7.0 # Apache-2.0
-sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
+reno>=1.8.0 # Apache2
+requests>=2.10.0 # Apache-2.0
+requests-mock>=1.0 # Apache-2.0
+sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
+stevedore>=1.16.0 # Apache-2.0
+os-client-config!=1.19.0,!=1.19.1,!=1.20.0,!=1.20.1,!=1.21.0,>=1.13.1 # Apache-2.0
os-testr>=0.7.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
-tempest>=11.0.0 # Apache-2.0
-osprofiler>=1.3.0 # Apache-2.0
-bandit>=1.0.1 # Apache-2.0
+tempest>=12.1.0 # Apache-2.0
+osprofiler>=1.4.0 # Apache-2.0
+bandit>=1.1.0 # Apache-2.0
+wrapt>=1.7.0 # BSD License
# Install these to generate sphinx autodocs
+aodhclient>=0.5.0 # Apache-2.0
python-barbicanclient>=4.0.0 # Apache-2.0
-python-congressclient<2000,>=1.0.0 # Apache-2.0
+python-congressclient<2000,>=1.3.0 # Apache-2.0
python-designateclient>=1.5.0 # Apache-2.0
-python-heatclient>=1.1.0 # Apache-2.0
-python-ironicclient>=1.1.0 # Apache-2.0
+python-heatclient>=1.4.0 # Apache-2.0
+python-ironicclient>=1.6.0 # Apache-2.0
python-ironic-inspector-client>=1.5.0 # Apache-2.0
-python-mistralclient>=1.0.0 # Apache-2.0
+python-mistralclient>=2.0.0 # Apache-2.0
python-muranoclient>=0.8.2 # Apache-2.0
-python-saharaclient>=0.13.0 # Apache-2.0
+python-neutronclient>=5.1.0 # Apache-2.0
+python-saharaclient>=0.16.0 # Apache-2.0
python-searchlightclient>=0.2.0 #Apache-2.0
python-senlinclient>=0.3.0 # Apache-2.0
python-zaqarclient>=1.0.0 # Apache-2.0
diff --git a/tools/tox_install.sh b/tools/tox_install.sh
new file mode 100755
index 0000000..4af3be1
--- /dev/null
+++ b/tools/tox_install.sh
@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+
+# Client constraint file contains this client version pin that is in conflict
+# with installing the client from source. We should replace the version pin in
+# the constraints file before applying it for from-source installation.
+
+ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner
+BRANCH_NAME=master
+CLIENT_NAME=python-openstackclient
+requirements_installed=$(echo "import openstack_requirements" | python 2>/dev/null ; echo $?)
+
+set -e
+
+CONSTRAINTS_FILE=$1
+shift
+
+install_cmd="pip install"
+mydir=$(mktemp -dt "$CLIENT_NAME-tox_install-XXXXXXX")
+trap "rm -rf $mydir" EXIT
+localfile=$mydir/upper-constraints.txt
+if [[ $CONSTRAINTS_FILE != http* ]]; then
+ CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE
+fi
+curl $CONSTRAINTS_FILE -k -o $localfile
+install_cmd="$install_cmd -c$localfile"
+
+if [ $requirements_installed -eq 0 ]; then
+ echo "ALREADY INSTALLED" > /tmp/tox_install.txt
+ echo "Requirements already installed; using existing package"
+elif [ -x "$ZUUL_CLONER" ]; then
+ echo "ZUUL CLONER" > /tmp/tox_install.txt
+ pushd $mydir
+ $ZUUL_CLONER --cache-dir \
+ /opt/git \
+ --branch $BRANCH_NAME \
+ git://git.openstack.org \
+ openstack/requirements
+ cd openstack/requirements
+ $install_cmd -e .
+ popd
+else
+ echo "PIP HARDCODE" > /tmp/tox_install.txt
+ if [ -z "$REQUIREMENTS_PIP_LOCATION" ]; then
+ REQUIREMENTS_PIP_LOCATION="git+https://git.openstack.org/openstack/requirements@$BRANCH_NAME#egg=requirements"
+ fi
+ $install_cmd -U -e ${REQUIREMENTS_PIP_LOCATION}
+fi
+
+# This is the main purpose of the script: Allow local installation of
+# the current repo. It is listed in constraints file and thus any
+# install will be constrained and we need to unconstrain it.
+edit-constraints $localfile -- $CLIENT_NAME "-e file://$PWD#egg=$CLIENT_NAME"
+
+$install_cmd -U $*
+exit $?
diff --git a/tox.ini b/tox.ini
index 6dfa126..9eb0a1a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,11 +1,12 @@
[tox]
minversion = 1.6
-envlist = py34,py27,pep8
+envlist = py35,py34,py27,pep8
skipdist = True
[testenv]
usedevelop = True
-install_command = pip install -U {opts} {packages}
+install_command =
+ {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/test-requirements.txt
commands = ostestr {posargs}
@@ -72,7 +73,7 @@ commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasen
[flake8]
show-source = True
-exclude = .git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools
+exclude = .git,.tox,dist,doc,*lib/python*,*egg,build,tools
# If 'ignore' is not set there are default errors and warnings that are set
# Doc: http://flake8.readthedocs.org/en/latest/config.html#default
ignore = __