diff options
| author | Ubuntu Git Importer <usd-importer-do-not-mail@canonical.com> | 2020-07-02 17:11:19 +0000 |
|---|---|---|
| committer | Ubuntu Git Importer <usd-importer-do-not-mail@canonical.com> | 2020-07-02 17:11:19 +0000 |
| commit | 9c972f002555f486463f0411646980a59ca8b68a (patch) | |
| tree | 765ba21333f33d92545a4f1a023bfcf6f9dde430 | |
| parent | 04b346535fea2f2b946425a89b21463d3583b0ab (diff) | |
New upstream version 2.6.0upstream/ubuntu/2.6.0.xz
121 files changed, 3914 insertions, 2356 deletions
diff --git a/doc/source/command-beta.rst b/doc/source/command-beta.rst index 53a4420..f0a4f85 100644 --- a/doc/source/command-beta.rst +++ b/doc/source/command-beta.rst @@ -33,7 +33,7 @@ example list List examples .. caution:: This is a beta command and subject to change. - Use global option ``--enable-beta-commands`` to + Use global option ``--os-beta-command`` to enable this command. .. program:: example list @@ -52,7 +52,7 @@ The command help must label the command as a beta. """Display example details (Caution: This is a beta command and subject to change. - Use global option --enable-beta-commands to enable + Use global option --os-beta-command to enable this command) """ @@ -60,13 +60,9 @@ Implementation -------------- The command must raise a ``CommandError`` exception if beta commands -are not enabled via ``--enable-beta-commands`` global option. +are not enabled via ``--os-beta-command`` global option. .. code-block:: python def take_action(self, parsed_args): - if not self.app.options.enable_beta_commands: - msg = _('Caution: This is a beta command and subject to ' - 'change. Use global option --enable-beta-commands ' - 'to enable this command.') - raise exceptions.CommandError(msg) + self.validate_os_beta_command_enabled() diff --git a/doc/source/command-objects/address-scope.rst b/doc/source/command-objects/address-scope.rst index a2ab566..7481ed5 100644 --- a/doc/source/command-objects/address-scope.rst +++ b/doc/source/command-objects/address-scope.rst @@ -50,18 +50,18 @@ Create new address scope address scope delete -------------------- -Delete an address scope +Delete address scope(s) .. program:: address scope delete .. code:: bash os address scope delete - <address-scope> + <address-scope> [<address-scope> ...] .. _address_scope_delete-address-scope: .. describe:: <address-scope> - Address scope to delete (name or ID) + Address scope(s) to delete (name or ID) address scope list ------------------ diff --git a/doc/source/command-objects/compute-agent.rst b/doc/source/command-objects/compute-agent.rst index 395ee4f..b00a981 100644 --- a/doc/source/command-objects/compute-agent.rst +++ b/doc/source/command-objects/compute-agent.rst @@ -44,17 +44,17 @@ Create compute agent compute agent delete -------------------- -Delete compute agent command +Delete compute agent(s) .. program:: compute agent delete .. code:: bash - os compute agent delete <id> + os compute agent delete <id> [<id> ...] .. _compute_agent-delete: .. describe:: <id> - ID of agent to delete + ID of agent(s) to delete compute agent list ------------------ diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst index bda64c8..71225bc 100644 --- a/doc/source/command-objects/compute-service.rst +++ b/doc/source/command-objects/compute-service.rst @@ -63,7 +63,7 @@ Set service command .. _compute-service-set: .. option:: --enable - Enable service (default) + Enable service .. option:: --disable @@ -71,8 +71,7 @@ Set service command .. option:: --disable-reason <reason> - Reason for disabling the service (in quotes). Note that when the service - is enabled, this option is ignored. + Reason for disabling the service (in quotes). Should be used with --disable option. .. describe:: <host> diff --git a/doc/source/command-objects/endpoint.rst b/doc/source/command-objects/endpoint.rst index 074f20a..9872a58 100644 --- a/doc/source/command-objects/endpoint.rst +++ b/doc/source/command-objects/endpoint.rst @@ -188,9 +188,9 @@ Display endpoint details .. code:: bash os endpoint show - <endpoint-id> + <endpoint> .. _endpoint_show-endpoint: -.. describe:: <endpoint-id> +.. describe:: <endpoint> - Endpoint ID to display + Endpoint to display (endpoint ID, service ID, service name, service type) diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index 322943d..a30bede 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -128,12 +128,23 @@ Set flavor properties os flavor set [--property <key=value> [...] ] + [--project <project>] + [--project-domain <project-domain>] <flavor> .. option:: --property <key=value> Property to add or modify for this flavor (repeat option to set multiple properties) +.. option:: --project <project> + + Set flavor access to project (name or ID) (admin 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. + .. describe:: <flavor> Flavor to modify (name or ID) @@ -164,12 +175,23 @@ Unset flavor properties os flavor unset [--property <key> [...] ] + [--project <project>] + [--project-domain <project-domain>] <flavor> .. option:: --property <key> Property to remove from flavor (repeat option to remove multiple properties) +.. option:: --project <project> + + Remove flavor access from project (name or ID) (admin 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. + .. describe:: <flavor> Flavor to modify (name or ID) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 942c03d..d6451af 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -499,3 +499,30 @@ Display image details .. describe:: <image> Image to display (name or ID) + +image unset +----------- + +*Only supported for Image v2* + +Unset image tags or properties + +.. program:: image unset +.. code:: bash + + os image set + [--tag <tag>] + [--property <property>] + <image> + +.. option:: --tag <tag> + + Unset a tag on this image (repeat option to unset multiple tags) + +.. option:: --property <property> + + Unset a property on this image (repeat option to unset multiple properties) + +.. describe:: <image> + + Image to modify (name or ID) diff --git a/doc/source/specs/ip-availability.rst b/doc/source/command-objects/ip-availability.rst index cf0c71f..55b7842 100644 --- a/doc/source/specs/ip-availability.rst +++ b/doc/source/command-objects/ip-availability.rst @@ -24,12 +24,12 @@ number of allocated IP addresses from that pool. .. option:: --ip-version {4,6} - List IP availability for specific version + List IP availability of given IP version networks (Default is 4) .. option:: --project <project> - List IP availability for specific project + List IP availability of given project (name or ID) ip availability show @@ -57,5 +57,4 @@ subnet within the network as well. .. _ip_availability_show-network .. describe:: <network> - Show network IP availability for specific - network (name or ID) + Show IP availability for a specific network (name or ID) diff --git a/doc/source/command-objects/keypair.rst b/doc/source/command-objects/keypair.rst index e49acac..6638b8c 100644 --- a/doc/source/command-objects/keypair.rst +++ b/doc/source/command-objects/keypair.rst @@ -40,7 +40,7 @@ Delete public key .. describe:: <key> - Public key to delete + Public key to delete (name only) keypair list ------------ @@ -66,7 +66,7 @@ Display public key details .. option:: --public-key - Show only bare public key + Show only bare public key (name only) .. describe:: <key> diff --git a/doc/source/command-objects/network-segment.rst b/doc/source/command-objects/network-segment.rst new file mode 100644 index 0000000..56a11e1 --- /dev/null +++ b/doc/source/command-objects/network-segment.rst @@ -0,0 +1,54 @@ +=============== +network segment +=============== + +A **network segment** is an isolated Layer 2 segment within a network. +A network may contain multiple network segments. Depending on the +network configuration, Layer 2 connectivity between network segments +within a network may not be guaranteed. + +Network v2 + +network segment list +-------------------- + +List network segments + +.. caution:: This is a beta command and subject to change. + Use global option ``--os-beta-command`` to + enable this command. + +.. program:: network segment list +.. code:: bash + + os network segment list + [--long] + [--network <network>] + +.. option:: --long + + List additional fields in output + +.. option:: --network <network> + + List network segments that belong to this network (name or ID) + +network segment show +-------------------- + +Display network segment details + +.. caution:: This is a beta command and subject to change. + Use global option ``--os-beta-command`` to + enable this command. + +.. program:: network segment show +.. code:: bash + + os network segment show + <network-segment> + +.. _network_segment_show-segment: +.. describe:: <network-segment> + + Network segment to display (ID only) diff --git a/doc/source/command-objects/server-backup.rst b/doc/source/command-objects/server-backup.rst new file mode 100644 index 0000000..23e17d5 --- /dev/null +++ b/doc/source/command-objects/server-backup.rst @@ -0,0 +1,44 @@ +============= +server backup +============= + +A server backup is a disk image created in the Image store from a running server +instance. The backup command manages the number of archival copies to retain. + +Compute v2 + +server backup create +-------------------- + +Create a server backup image + +.. program:: server create +.. code:: bash + + os server backup create + [--name <image-name>] + [--type <backup-type>] + [--rotate <count>] + [--wait] + <server> + +.. option:: --name <image-name> + + Name of the backup image (default: server name) + +.. option:: --type <backup-type> + + Used to populate the ``backup_type`` property of the backup + image (default: empty) + +.. option:: --rotate <count> + + Number of backup images to keep (default: 1) + +.. option:: --wait + + Wait for operation to complete + +.. describe:: <server> + + Server to back up (name or ID) diff --git a/doc/source/command-objects/server-group.rst b/doc/source/command-objects/server-group.rst index 55d789c..414f49b 100644 --- a/doc/source/command-objects/server-group.rst +++ b/doc/source/command-objects/server-group.rst @@ -31,7 +31,7 @@ Create a new server group server group delete ------------------- -Delete an existing server group +Delete existing server group(s) .. program:: server group delete .. code-block:: bash diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index bf97298..f11355b 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -559,6 +559,7 @@ Set server properties --property <key=value> [--property <key=value>] ... --root-password + --state <state> <server> .. option:: --name <new-name> @@ -574,6 +575,10 @@ Set server properties Property to add/change for this server (repeat option to set multiple properties) +.. option:: --state <state> + + New server state (valid value: active, error) + .. describe:: <server> Server (name or ID) diff --git a/doc/source/command-objects/volume-transfer-request.rst b/doc/source/command-objects/volume-transfer-request.rst new file mode 100644 index 0000000..79d84f7 --- /dev/null +++ b/doc/source/command-objects/volume-transfer-request.rst @@ -0,0 +1,21 @@ +======================= +volume transfer request +======================= + +Block Storage v1, v2 + +volume transfer request list +---------------------------- + +Lists all volume transfer requests. + +.. program:: volume transfer request list +.. code:: bash + + os volume transfer request list + --all-projects + +.. option:: --all-projects + + Shows detail for all projects. Admin only. + (defaults to False)
\ No newline at end of file diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index b7aea63..50acf9f 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -150,7 +150,7 @@ Unset volume type properties .. code:: bash os volume type unset - [--property <key>] + [--property <key> [...] ] [--project <project>] [--project-domain <project-domain>] <volume-type> diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 12542d1..9fb0555 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -96,6 +96,7 @@ referring to both Compute and Volume quotas. * ``hypervisor stats``: (**Compute**) hypervisor statistics over all compute nodes * ``identity provider``: (**Identity**) a source of users and authentication * ``image``: (**Image**) a disk image +* ``ip availability``: (**Network**) - details of IP usage of a network * ``ip fixed``: (**Compute**, **Network**) - an internal IP address assigned to a server * ``ip floating``: (**Compute**, **Network**) - a public IP address that can be mapped to a server * ``ip floating pool``: (**Compute**, **Network**) - a pool of public IP addresses @@ -104,6 +105,7 @@ 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 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 * ``policy``: (**Identity**) determines authorization @@ -118,6 +120,7 @@ referring to both Compute and Volume quotas. * ``security group``: (**Compute**, **Network**) - groups of network access rules * ``security group rule``: (**Compute**, **Network**) - the individual rules that define protocol/IP/port access * ``server``: (**Compute**) virtual machine instance +* ``server backup``: (**Compute**) backup server disk image by using snapshot method * ``server dump``: (**Compute**) a dump file of a server created by features like kdump * ``server group``: (**Compute**) a grouping of servers * ``server image``: (**Compute**) saved server disk image @@ -135,6 +138,7 @@ referring to both Compute and Volume quotas. * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes * ``volume type``: (**Volume**) deployment-specific types of volumes available * ``volume service``: (**Volume**) services to manage block storage operations +* ``volume transfer request``: (**Volume**) volume owner transfer request Plugin Objects diff --git a/doc/source/index.rst b/doc/source/index.rst index 0869344..5889553 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -37,7 +37,7 @@ Release Notes .. toctree:: :maxdepth: 1 - releases + Release Notes <http://docs.openstack.org/releasenotes/python-openstackclient> history Developer Documentation diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index 8cd10a7..7af380a 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -82,7 +82,7 @@ OPTIONS Project-level authentication scope (name or ID) :option:`--os-project-domain-name` <auth-project-domain-name> | :option:`--os-project-domain-id` <auth-project-domain-id> - Domain name or id containing project + Domain name or ID containing project :option:`--os-username` <auth-username> Authentication username @@ -94,13 +94,10 @@ OPTIONS Authenticated token or service token :option:`--os-user-domain-name` <auth-user-domain-name> | :option:`--os-user-domain-id` <auth-user-domain-id> - Domain name or id containing user - -:option:`--os-user-domain-name` <auth-user-domain-name> | :option:`--os-user-domain-id` <auth-user-domain-id> Domain name or ID containing user :option:`--os-trust-id` <trust-id> - id of the trust to use as a trustee user + ID of the trust to use as a trustee user :option:`--os-default-domain` <auth-domain> Default domain ID (Default: 'default') @@ -135,6 +132,9 @@ OPTIONS This key should be the value of one of the HMAC keys defined in the configuration files of OpenStack services to be traced. +:option:`--os-beta-command` + Enable beta commands which are subject to change + :option:`--log-file` <LOGFILE> Specify a file to log output. Disabled by default. @@ -142,13 +142,10 @@ OPTIONS Increase verbosity of output. Can be repeated. :option:`-q, --quiet` - suppress output except warnings and errors + Suppress output except warnings and errors :option:`--debug` - show tracebacks on errors and set verbosity to debug - -:option:`--enable-beta-commands` - Enable beta commands which are subject to change + Show tracebacks on errors and set verbosity to debug COMMANDS ======== @@ -350,7 +347,7 @@ The following environment variables can be set to alter the behaviour of :progra Project-level authentication scope (name or ID) :envvar:`OS_PROJECT_DOMAIN_NAME` - Domain name or id containing project + Domain name or ID containing project :envvar:`OS_USERNAME` Authentication username @@ -362,10 +359,10 @@ The following environment variables can be set to alter the behaviour of :progra Authentication password :envvar:`OS_USER_DOMAIN_NAME` - Domain name or id containing user + Domain name or ID containing user :envvar:`OS_TRUST_ID` - id of the trust to use as a trustee user + ID of the trust to use as a trustee user :envvar:`OS_DEFAULT_DOMAIN` Default domain ID (Default: 'default') diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index 6cb82cb..96c1c1a 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -129,6 +129,8 @@ class ServerTests(test.TestCase): 1) Boot server in setUp 2) Set properties for server 3) Check server properties in server show output + 4) Unset properties for server + 5) Check server properties in server show output """ self.wait_for_status("ACTIVE") # metadata @@ -138,6 +140,12 @@ class ServerTests(test.TestCase): 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"]) + raw_output = self.openstack('server show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\nc='d'\n", raw_output) + def test_server_suspend_resume(self): """Test server suspend and resume commands. diff --git a/functional/tests/image/v2/test_image.py b/functional/tests/image/v2/test_image.py index f0ebc11..6a33ad8 100644 --- a/functional/tests/image/v2/test_image.py +++ b/functional/tests/image/v2/test_image.py @@ -65,3 +65,12 @@ class ImageTests(test.TestCase): 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"]) + 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" + self.openstack('image unset --property a --property c ' + self.NAME) + raw_output = self.openstack('image show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n\n", raw_output) diff --git a/functional/tests/network/v2/test_ip_availability.py b/functional/tests/network/v2/test_ip_availability.py new file mode 100644 index 0000000..f1302d5 --- /dev/null +++ b/functional/tests/network/v2/test_ip_availability.py @@ -0,0 +1,53 @@ +# 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 IPAvailabilityTests(test.TestCase): + """Functional tests for IP availability. """ + NAME = uuid.uuid4().hex + NETWORK_NAME = uuid.uuid4().hex + FIELDS = ['network_name'] + + @classmethod + def setUpClass(cls): + # Create a network for the subnet. + cls.openstack('network create ' + cls.NETWORK_NAME) + opts = cls.get_show_opts(['name']) + raw_output = cls.openstack( + 'subnet create --network ' + cls.NETWORK_NAME + + ' --subnet-range 10.10.10.0/24 ' + + cls.NAME + opts + ) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_subnet = cls.openstack('subnet delete ' + cls.NAME) + raw_network = cls.openstack('network delete ' + cls.NETWORK_NAME) + cls.assertOutput('', raw_subnet) + cls.assertOutput('', raw_network) + + def test_ip_availability_list(self): + opts = ' -f csv -c "Network Name"' + raw_output = self.openstack('ip availability list' + opts) + self.assertIn(self.NETWORK_NAME, raw_output) + + def test_ip_availability_show(self): + opts = self.get_show_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_segment.py b/functional/tests/network/v2/test_network_segment.py new file mode 100644 index 0000000..b5b5dcd --- /dev/null +++ b/functional/tests/network/v2/test_network_segment.py @@ -0,0 +1,60 @@ +# 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 testtools +import uuid + +from functional.common import test + + +# NOTE(rtheis): Routed networks is still a WIP and not enabled by default. +@testtools.skip("bp/routed-networks") +class NetworkSegmentTests(test.TestCase): + """Functional tests for network segment. """ + NETWORK_NAME = uuid.uuid4().hex + PHYSICAL_NETWORK_NAME = uuid.uuid4().hex + NETWORK_SEGMENT_ID = None + NETWORK_ID = None + + @classmethod + def setUpClass(cls): + # Create a network for the segment. + opts = cls.get_show_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']) + raw_output = cls.openstack('--os-beta-command ' + 'network segment list ' + ' --network ' + cls.NETWORK_NAME + + ' ' + opts) + raw_output_row = raw_output.split('\n')[0] + cls.NETWORK_SEGMENT_ID = raw_output_row.split(' ')[0] + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) + cls.assertOutput('', raw_output) + + def test_network_segment_list(self): + opts = self.get_list_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']) + raw_output = self.openstack('--os-beta-command ' + 'network segment show ' + + self.NETWORK_SEGMENT_ID + opts) + self.assertEqual(self.NETWORK_ID + "\n", raw_output) diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index fee4559..144a0db 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -20,6 +20,9 @@ from cliff import lister from cliff import show import six +from openstackclient.common import exceptions +from openstackclient.i18n import _ + class CommandMeta(abc.ABCMeta): @@ -37,6 +40,13 @@ class Command(command.Command): 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 diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index e177fbc..f85d550 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -142,7 +142,7 @@ class SetQuota(command.Command): if (compute_kwargs == {} and volume_kwargs == {} and network_kwargs == {}): - sys.stderr.write("No quotas updated") + sys.stderr.write("No quotas updated\n") return if parsed_args.project: diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 82f09ce..8e6eedc 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -17,6 +17,7 @@ import logging from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.i18n import _ LOG = logging.getLogger(__name__) @@ -82,9 +83,9 @@ def build_option_parser(parser): '--os-compute-api-version', metavar='<compute-api-version>', default=utils.env('OS_COMPUTE_API_VERSION'), - help='Compute API version, default=' + - DEFAULT_API_VERSION + - ' (Env: OS_COMPUTE_API_VERSION)') + help=_("Compute API version, default=%s " + "(Env: OS_COMPUTE_API_VERSION)") % DEFAULT_API_VERSION + ) return parser @@ -118,10 +119,9 @@ def check_api_version(check_version): novaclient.API_MIN_VERSION, novaclient.API_MAX_VERSION, ): - raise exceptions.CommandError( - "versions supported by client: %s - %s" % ( - novaclient.API_MIN_VERSION.get_string(), - novaclient.API_MAX_VERSION.get_string(), - ), - ) + msg = _("versions supported by client: %(min)s - %(max)s") % { + "min": novaclient.API_MIN_VERSION.get_string(), + "max": novaclient.API_MAX_VERSION.get_string(), + } + raise exceptions.CommandError(msg) return True diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index e358399..064fe5a 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -18,6 +18,7 @@ import six from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ @@ -75,20 +76,35 @@ class CreateAgent(command.ShowOne): class DeleteAgent(command.Command): - """Delete compute agent command""" + """Delete compute agent(s)""" def get_parser(self, prog_name): parser = super(DeleteAgent, self).get_parser(prog_name) parser.add_argument( "id", metavar="<id>", - help=_("ID of agent to delete") + nargs='+', + help=_("ID of agent(s) to delete") ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - compute_client.agents.delete(parsed_args.id) + result = 0 + for id in parsed_args.id: + try: + 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}) + + if result > 0: + total = len(parsed_args.id) + msg = (_("%(result)s of %(total)s agents failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListAgent(command.Lister): diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 37ff831..87909a1 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -22,6 +22,7 @@ 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 def _find_flavor(compute_client, flavor): @@ -246,6 +247,11 @@ class SetFlavor(command.Command): def get_parser(self, prog_name): parser = super(SetFlavor, self).get_parser(prog_name) parser.add_argument( + "flavor", + metavar="<flavor>", + help=_("Flavor to modify (name or ID)") + ) + parser.add_argument( "--property", metavar="<key=value>", action=parseractions.KeyValueAction, @@ -253,16 +259,54 @@ class SetFlavor(command.Command): "(repeat option to set multiple properties)") ) parser.add_argument( - "flavor", - metavar="<flavor>", - help=_("Flavor to modify (name or ID)") + '--project', + metavar='<project>', + help=_('Set flavor access to project (name or ID) ' + '(admin only)'), ) + 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 + flavor = _find_flavor(compute_client, parsed_args.flavor) - flavor.set_keys(parsed_args.property) + + 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)) + result += 1 + + if parsed_args.project: + try: + if flavor.is_public: + msg = _("Cannot set access for a public flavor") + raise exceptions.CommandError(msg) + else: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + 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)) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("Command Failed: One or more of" + " the operations failed")) class ShowFlavor(command.ShowOne): @@ -294,21 +338,63 @@ class UnsetFlavor(command.Command): def get_parser(self, prog_name): parser = super(UnsetFlavor, self).get_parser(prog_name) parser.add_argument( + "flavor", + metavar="<flavor>", + help=_("Flavor to modify (name or ID)") + ) + parser.add_argument( "--property", metavar="<key>", action='append', - required=True, help=_("Property to remove from flavor " "(repeat option to unset multiple properties)") ) parser.add_argument( - "flavor", - metavar="<flavor>", - help=_("Flavor to modify (name or ID)") + '--project', + metavar='<project>', + help=_('Remove flavor access from project (name or ID) ' + '(admin only)'), ) + 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 + flavor = _find_flavor(compute_client, parsed_args.flavor) - flavor.unset_keys(parsed_args.property) + + 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)) + result += 1 + + if parsed_args.project: + try: + if flavor.is_public: + msg = _("Cannot remove access for a public flavor") + raise exceptions.CommandError(msg) + else: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + 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)) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("Command Failed: One or more of" + " the operations failed")) diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 8a58e8f..8af209f 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -84,7 +84,7 @@ class DeleteKeypair(command.Command): parser.add_argument( 'name', metavar='<key>', - help=_("Public key to delete") + help=_("Public key to delete (name only)") ) return parser @@ -118,7 +118,7 @@ class ShowKeypair(command.ShowOne): parser.add_argument( 'name', metavar='<key>', - help=_("Public key to display") + help=_("Public key to display (name only)") ) parser.add_argument( '--public-key', diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 781ccb1..5688b55 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -562,7 +562,7 @@ class CreateServer(command.ShowOne): else: self.log.error(_('Error creating server: %s'), parsed_args.server_name) - sys.stdout.write(_('\nError creating server')) + sys.stdout.write(_('Error creating server\n')) raise SystemExit details = _prep_server_detail(compute_client, server) @@ -646,7 +646,7 @@ class CreateServerImage(command.ShowOne): else: self.log.error(_('Error creating snapshot of server: %s'), parsed_args.server) - sys.stdout.write(_('\nError creating server snapshot')) + sys.stdout.write(_('Error creating server snapshot\n')) raise SystemExit image = _prep_image_detail(image_client, image_id) @@ -688,7 +688,7 @@ class DeleteServer(command.Command): else: self.log.error(_('Error deleting server: %s'), server_obj.id) - sys.stdout.write(_('\nError deleting server')) + sys.stdout.write(_('Error deleting server\n')) raise SystemExit @@ -1013,7 +1013,9 @@ class MigrateServer(command.Command): ): sys.stdout.write(_('Complete\n')) else: - sys.stdout.write(_('\nError migrating server')) + self.log.error(_('Error migrating server: %s'), + server.id) + sys.stdout.write(_('Error migrating server\n')) raise SystemExit @@ -1085,9 +1087,11 @@ class RebootServer(command.Command): server.id, callback=_show_progress, ): - sys.stdout.write(_('\nReboot complete\n')) + sys.stdout.write(_('Complete\n')) else: - sys.stdout.write(_('\nError rebooting server\n')) + self.log.error(_('Error rebooting server: %s'), + server.id) + sys.stdout.write(_('Error rebooting server\n')) raise SystemExit @@ -1136,9 +1140,11 @@ class RebuildServer(command.ShowOne): server.id, callback=_show_progress, ): - sys.stdout.write(_('\nComplete\n')) + sys.stdout.write(_('Complete\n')) else: - sys.stdout.write(_('\nError rebuilding server')) + self.log.error(_('Error rebuilding server: %s'), + server.id) + sys.stdout.write(_('Error rebuilding server\n')) raise SystemExit details = _prep_server_detail(compute_client, server) @@ -1290,7 +1296,9 @@ class ResizeServer(command.Command): ): sys.stdout.write(_('Complete\n')) else: - sys.stdout.write(_('\nError resizing server')) + self.log.error(_('Error resizing server: %s'), + server.id) + sys.stdout.write(_('Error resizing server\n')) raise SystemExit elif parsed_args.confirm: compute_client.servers.confirm_resize(server) @@ -1370,6 +1378,12 @@ class SetServer(command.Command): help=_('Property to add/change for this server ' '(repeat option to set multiple properties)'), ) + parser.add_argument( + '--state', + metavar='<state>', + choices=['active', 'error'], + help=_('New server state (valid value: active, error)'), + ) return parser def take_action(self, parsed_args): @@ -1389,6 +1403,9 @@ class SetServer(command.Command): parsed_args.property, ) + if parsed_args.state: + server.reset_state(state=parsed_args.state) + if parsed_args.root_password: p1 = getpass.getpass(_('New password: ')) p2 = getpass.getpass(_('Retype new password: ')) @@ -1447,7 +1464,7 @@ class ShowServer(command.ShowOne): if parsed_args.diagnostics: (resp, data) = server.diagnostics() if not resp.status_code == 200: - sys.stderr.write(_("Error retrieving diagnostics data")) + sys.stderr.write(_("Error retrieving diagnostics data\n")) return ({}, {}) else: data = _prep_server_detail(compute_client, server) diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py new file mode 100644 index 0000000..24d7101 --- /dev/null +++ b/openstackclient/compute/v2/server_backup.py @@ -0,0 +1,134 @@ +# 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 sys + +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 _ + + +def _show_progress(progress): + if progress: + sys.stderr.write('\rProgress: %s' % progress) + sys.stderr.flush() + + +class CreateServerBackup(command.ShowOne): + """Create a server backup image""" + + IMAGE_API_VERSIONS = { + "1": "openstackclient.image.v1.image", + "2": "openstackclient.image.v2.image", + } + + def get_parser(self, prog_name): + parser = super(CreateServerBackup, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + help=_('Server to back up (name or ID)'), + ) + parser.add_argument( + '--name', + metavar='<image-name>', + help=_('Name of the backup image (default: server name)'), + ) + parser.add_argument( + '--type', + metavar='<backup-type>', + help=_( + 'Used to populate the backup_type property of the backup ' + 'image (default: empty)' + ), + ) + parser.add_argument( + '--rotate', + metavar='<count>', + type=int, + help=_('Number of backups to keep (default: 1)'), + ) + parser.add_argument( + '--wait', + action='store_true', + help=_('Wait for backup image create 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, + ) + + # Set sane defaults as this API wants all mouths to be fed + if parsed_args.name is None: + backup_name = server.name + else: + backup_name = parsed_args.name + if parsed_args.type is None: + backup_type = "" + else: + backup_type = parsed_args.type + if parsed_args.rotate is None: + backup_rotation = 1 + else: + backup_rotation = parsed_args.rotate + + compute_client.servers.backup( + server.id, + backup_name, + backup_type, + backup_rotation, + ) + + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + backup_name, + ) + + if parsed_args.wait: + if utils.wait_for_status( + image_client.images.get, + image.id, + callback=_show_progress, + ): + sys.stdout.write('\n') + else: + msg = _('Error creating server backup: %s') % parsed_args.name + raise exceptions.CommandError(msg) + + 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/server_group.py b/openstackclient/compute/v2/server_group.py index 973095c..2e275b7 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -71,7 +71,7 @@ class CreateServerGroup(command.ShowOne): class DeleteServerGroup(command.Command): - """Delete an existing server group.""" + """Delete existing server group(s).""" def get_parser(self, prog_name): parser = super(DeleteServerGroup, self).get_parser(prog_name) @@ -129,15 +129,7 @@ class ListServerGroup(command.Lister): data = compute_client.server_groups.list(parsed_args.all_projects) if parsed_args.long: - column_headers = ( - 'ID', - 'Name', - 'Policies', - 'Members', - 'Project Id', - 'User Id', - ) - columns = ( + column_headers = columns = ( 'ID', 'Name', 'Policies', diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 6093b8c..e7966a8 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -16,6 +16,7 @@ """Service action implementations""" from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ @@ -110,23 +111,20 @@ class SetService(command.Command): enabled_group = parser.add_mutually_exclusive_group() enabled_group.add_argument( "--enable", - dest="enabled", - default=True, action="store_true", - help=_("Enable a service (default)") + help=_("Enable service") ) enabled_group.add_argument( "--disable", - dest="enabled", - action="store_false", - help=_("Disable a service") + action="store_true", + help=_("Disable service") ) parser.add_argument( "--disable-reason", default=None, metavar="<reason>", - help=_("Reason for disabling the service (in quotas). Note that " - "when the service is enabled, this option is ignored.") + help=_("Reason for disabling the service (in quotas). " + "Should be used with --disable option.") ) return parser @@ -134,16 +132,26 @@ class SetService(command.Command): compute_client = self.app.client_manager.compute cs = compute_client.services - if not parsed_args.enabled: + if (parsed_args.enable or not parsed_args.disable) and \ + parsed_args.disable_reason: + msg = _("Cannot specify option --disable-reason without " + "--disable specified.") + raise exceptions.CommandError(msg) + + 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) - else: - if parsed_args.disable_reason: - msg = _("argument --disable-reason has been ignored") - self.log.info(msg) - - cs.enable(parsed_args.host, parsed_args.service) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 53a6fe3..c8f48cb 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -89,7 +89,7 @@ class ShowCatalog(command.ShowOne): break if not data: - self.app.log.error('service %s not found\n' % + self.app.log.error(_('service %s not found\n') % parsed_args.service) return ([], []) diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index e515fc9..09ea738 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -81,7 +81,8 @@ class DeleteEndpoint(command.Command): parser.add_argument( 'endpoint', metavar='<endpoint-id>', - help=_('Endpoint ID to delete')) + help=_('Endpoint ID to delete'), + ) return parser def take_action(self, parsed_args): @@ -129,8 +130,9 @@ class ShowEndpoint(command.ShowOne): parser = super(ShowEndpoint, self).get_parser(prog_name) parser.add_argument( 'endpoint_or_service', - metavar='<endpoint-id>', - help=_('Endpoint ID to display'), + metavar='<endpoint>', + help=_('Endpoint to display (endpoint ID, service ID,' + ' service name, service type)'), ) return parser diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index d90162c..80f88d7 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -88,7 +88,7 @@ class CreateProject(command.ShowOne): identity_client.tenants, parsed_args.name, ) - self.log.info('Returning existing project %s', project.name) + self.log.info(_('Returning existing project %s'), project.name) else: raise e @@ -231,7 +231,8 @@ class ShowProject(command.ShowOne): parser.add_argument( 'project', metavar='<project>', - help=_('Project to display (name or ID)')) + help=_('Project to display (name or ID)'), + ) return parser def take_action(self, parsed_args): @@ -295,9 +296,9 @@ class UnsetProject(command.Command): metavar='<key>', action='append', default=[], + required=True, help=_('Unset a project property ' '(repeat option to unset multiple properties)'), - required=True, ) return parser @@ -308,7 +309,7 @@ class UnsetProject(command.Command): parsed_args.project, ) if not parsed_args.property: - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) else: kwargs = project._info for key in parsed_args.property: diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 1fcee15..6b014d8 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -95,7 +95,7 @@ class CreateRole(command.ShowOne): identity_client.roles, parsed_args.role_name, ) - self.log.info('Returning existing role %s', role.name) + self.log.info(_('Returning existing role %s'), role.name) else: raise e @@ -136,12 +136,12 @@ class ListRole(command.Lister): parser.add_argument( '--project', metavar='<project>', - help='Filter roles by <project> (name or ID)', + help=_('Filter roles by <project> (name or ID)'), ) parser.add_argument( '--user', metavar='<user>', - help='Filter roles by <user> (name or ID)', + help=_('Filter roles by <user> (name or ID)'), ) return parser diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index bc9bf83..f8f5df2 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -104,7 +104,7 @@ class CreateUser(command.ShowOne): identity_client.users, parsed_args.name, ) - self.log.info('Returning existing user %s', user.name) + self.log.info(_('Returning existing user %s'), user.name) else: raise e @@ -159,7 +159,8 @@ class ListUser(command.Lister): '--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): diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index 78d71f5..4c79469 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -84,7 +84,7 @@ class ShowCatalog(command.ShowOne): break if not data: - self.app.log.error('service %s not found\n' % + self.app.log.error(_('service %s not found\n') % parsed_args.service) return ([], []) diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index 0da4103..a062b74 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -20,6 +20,7 @@ import sys from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreateConsumer(command.ShowOne): @@ -30,7 +31,7 @@ class CreateConsumer(command.ShowOne): parser.add_argument( '--description', metavar='<description>', - help='New consumer description', + help=_('New consumer description'), ) return parser @@ -51,7 +52,7 @@ class DeleteConsumer(command.Command): parser.add_argument( 'consumer', metavar='<consumer>', - help='Consumer to delete', + help=_('Consumer to delete'), ) return parser @@ -83,12 +84,12 @@ class SetConsumer(command.Command): parser.add_argument( 'consumer', metavar='<consumer>', - help='Consumer to modify', + help=_('Consumer to modify'), ) parser.add_argument( '--description', metavar='<description>', - help='New consumer description', + help=_('New consumer description'), ) return parser @@ -101,7 +102,7 @@ class SetConsumer(command.Command): kwargs['description'] = parsed_args.description if not len(kwargs): - sys.stdout.write('Consumer not updated, no arguments present') + sys.stdout.write(_('Consumer not updated, no arguments present\n')) return consumer = identity_client.oauth1.consumers.update( @@ -116,7 +117,7 @@ class ShowConsumer(command.ShowOne): parser.add_argument( 'consumer', metavar='<consumer>', - help='Consumer to display', + help=_('Consumer to display'), ) return parser diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index b0d2caf..9901347 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -19,6 +19,7 @@ import six from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreateCredential(command.ShowOne): @@ -29,24 +30,25 @@ class CreateCredential(command.ShowOne): parser.add_argument( 'user', metavar='<user>', - help='Name or ID of user that owns the credential', + help=_('Name or ID of user that owns the credential'), ) parser.add_argument( '--type', default="cert", metavar='<type>', choices=['ec2', 'cert'], - help='New credential type', + help=_('New credential type'), ) parser.add_argument( 'data', metavar='<data>', - help='New credential data', + help=_('New credential data'), ) parser.add_argument( '--project', metavar='<project>', - help='Project name or ID which limits the scope of the credential', + help=_('Project name or ID which limits the ' + 'scope of the credential'), ) return parser @@ -77,7 +79,7 @@ class DeleteCredential(command.Command): parser.add_argument( 'credential', metavar='<credential-id>', - help='ID of credential to delete', + help=_('ID of credential to delete'), ) return parser @@ -108,31 +110,32 @@ class SetCredential(command.Command): parser.add_argument( 'credential', metavar='<credential-id>', - help='ID of credential to change', + help=_('ID of credential to change'), ) parser.add_argument( '--user', metavar='<user>', required=True, - help='Name or ID of user that owns the credential', + help=_('Name or ID of user that owns the credential'), ) parser.add_argument( '--type', metavar='<type>', choices=['ec2', 'cert'], required=True, - help='New credential type', + help=_('New credential type'), ) parser.add_argument( '--data', metavar='<data>', required=True, - help='New credential data', + help=_('New credential data'), ) parser.add_argument( '--project', metavar='<project>', - help='Project name or ID which limits the scope of the credential', + help=_('Project name or ID which limits the ' + 'scope of the credential'), ) return parser @@ -163,7 +166,7 @@ class ShowCredential(command.ShowOne): parser.add_argument( 'credential', metavar='<credential-id>', - help='ID of credential to display', + help=_('ID of credential to display'), ) return parser diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 7fcab4f..c345028 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -33,23 +33,23 @@ class CreateDomain(command.ShowOne): parser.add_argument( 'name', metavar='<domain-name>', - help='New domain name', + help=_('New domain name'), ) parser.add_argument( '--description', metavar='<description>', - help='New domain description', + help=_('New domain description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable domain (default)', + help=_('Enable domain (default)'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable domain', + help=_('Disable domain'), ) parser.add_argument( '--or-show', @@ -75,7 +75,7 @@ class CreateDomain(command.ShowOne): if parsed_args.or_show: domain = utils.find_resource(identity_client.domains, parsed_args.name) - self.log.info('Returning existing domain %s', domain.name) + self.log.info(_('Returning existing domain %s'), domain.name) else: raise e @@ -91,7 +91,7 @@ class DeleteDomain(command.Command): parser.add_argument( 'domain', metavar='<domain>', - help='Domain to delete (name or ID)', + help=_('Domain to delete (name or ID)'), ) return parser @@ -123,28 +123,28 @@ class SetDomain(command.Command): parser.add_argument( 'domain', metavar='<domain>', - help='Domain to modify (name or ID)', + help=_('Domain to modify (name or ID)'), ) parser.add_argument( '--name', metavar='<name>', - help='New domain name', + help=_('New domain name'), ) parser.add_argument( '--description', metavar='<description>', - help='New domain description', + help=_('New domain description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable domain', + help=_('Enable domain'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable domain', + help=_('Disable domain'), ) return parser @@ -164,7 +164,7 @@ class SetDomain(command.Command): kwargs['enabled'] = False if not kwargs: - sys.stdout.write("Domain not updated, no arguments present") + sys.stdout.write(_("Domain not updated, no arguments present\n")) return identity_client.domains.update(domain.id, **kwargs) @@ -177,7 +177,7 @@ class ShowDomain(command.ShowOne): parser.add_argument( 'domain', metavar='<domain>', - help='Domain to display (name or ID)', + help=_('Domain to display (name or ID)'), ) return parser diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index a12b2d3..859ec2a 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -56,18 +56,14 @@ class CreateEC2Creds(command.ShowOne): parser.add_argument( '--project', metavar='<project>', - help=_( - 'Create credentials in project ' - '(name or ID; default: current authenticated project)' - ), + help=_('Create credentials in project ' + '(name or ID; default: current authenticated project)'), ) parser.add_argument( '--user', metavar='<user>', - help=_( - 'Create credentials for user ' - '(name or ID; default: current authenticated user)' - ), + help=_('Create credentials for user ' + '(name or ID; default: current authenticated user)'), ) common.add_user_domain_option_to_parser(parser) common.add_project_domain_option_to_parser(parser) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 6e4b356..39022d2 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -20,6 +20,7 @@ import sys from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common @@ -38,23 +39,23 @@ class CreateEndpoint(command.ShowOne): parser.add_argument( 'service', metavar='<service>', - help='New endpoint service (name or ID)', + help=_('New endpoint service (name or ID)'), ) parser.add_argument( 'interface', metavar='<interface>', choices=['admin', 'public', 'internal'], - help='New endpoint interface type (admin, public or internal)', + help=_('New endpoint interface type (admin, public or internal)'), ) parser.add_argument( 'url', metavar='<url>', - help='New endpoint URL', + help=_('New endpoint URL'), ) parser.add_argument( '--region', metavar='<region-id>', - help='New endpoint region ID', + help=_('New endpoint region ID'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( @@ -62,13 +63,13 @@ class CreateEndpoint(command.ShowOne): dest='enabled', action='store_true', default=True, - help='Enable endpoint (default)', + help=_('Enable endpoint (default)'), ) enable_group.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable endpoint', + help=_('Disable endpoint'), ) return parser @@ -100,7 +101,7 @@ class DeleteEndpoint(command.Command): parser.add_argument( 'endpoint', metavar='<endpoint-id>', - help='Endpoint ID to delete', + help=_('Endpoint ID to delete'), ) return parser @@ -119,18 +120,18 @@ class ListEndpoint(command.Lister): parser.add_argument( '--service', metavar='<service>', - help='Filter by service', + help=_('Filter by service'), ) parser.add_argument( '--interface', metavar='<interface>', choices=['admin', 'public', 'internal'], - help='Filter by interface type (admin, public or internal)', + help=_('Filter by interface type (admin, public or internal)'), ) parser.add_argument( '--region', metavar='<region-id>', - help='Filter by region ID', + help=_('Filter by region ID'), ) return parser @@ -167,41 +168,41 @@ class SetEndpoint(command.Command): parser.add_argument( 'endpoint', metavar='<endpoint-id>', - help='Endpoint ID to modify', + help=_('Endpoint ID to modify'), ) parser.add_argument( '--region', metavar='<region-id>', - help='New endpoint region ID', + help=_('New endpoint region ID'), ) parser.add_argument( '--interface', metavar='<interface>', choices=['admin', 'public', 'internal'], - help='New endpoint interface type (admin, public or internal)', + help=_('New endpoint interface type (admin, public or internal)'), ) parser.add_argument( '--url', metavar='<url>', - help='New endpoint URL', + help=_('New endpoint URL'), ) parser.add_argument( '--service', metavar='<service>', - help='New endpoint service (name or ID)', + help=_('New endpoint service (name or ID)'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', dest='enabled', action='store_true', - help='Enable endpoint', + help=_('Enable endpoint'), ) enable_group.add_argument( '--disable', dest='disabled', action='store_true', - help='Disable endpoint', + help=_('Disable endpoint'), ) return parser @@ -213,7 +214,7 @@ class SetEndpoint(command.Command): 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") + sys.stdout.write(_("Endpoint not updated, no arguments present\n")) return service_id = None @@ -243,8 +244,9 @@ class ShowEndpoint(command.ShowOne): parser = super(ShowEndpoint, self).get_parser(prog_name) parser.add_argument( 'endpoint', - metavar='<endpoint-id>', - help='Endpoint ID to display', + metavar='<endpoint>', + help=_('Endpoint to display (endpoint ID, service ID,' + ' service name, service type)'), ) return parser diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 27c837c..c0f4bc9 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -18,6 +18,7 @@ import six from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreateProtocol(command.ShowOne): @@ -28,19 +29,22 @@ class CreateProtocol(command.ShowOne): parser.add_argument( 'federation_protocol', metavar='<name>', - help='New federation protocol name (must be unique per identity ' - ' provider)') + help=_('New federation protocol name (must be unique ' + 'per identity provider)'), + ) parser.add_argument( '--identity-provider', metavar='<identity-provider>', required=True, - help='Identity provider that will support the new federation ' - ' protocol (name or ID) (required)') + help=_('Identity provider that will support the new federation ' + ' protocol (name or ID) (required)'), + ) parser.add_argument( '--mapping', metavar='<mapping>', required=True, - help='Mapping that is to be used (name or ID) (required)') + help=_('Mapping that is to be used (name or ID) (required)'), + ) return parser @@ -69,13 +73,15 @@ class DeleteProtocol(command.Command): parser.add_argument( 'federation_protocol', metavar='<federation-protocol>', - help='Federation protocol to delete (name or ID)') + help=_('Federation protocol to delete (name or ID)'), + ) parser.add_argument( '--identity-provider', metavar='<identity-provider>', required=True, - help='Identity provider that supports <federation-protocol> ' - '(name or ID) (required)') + help=_('Identity provider that supports <federation-protocol> ' + '(name or ID) (required)'), + ) return parser @@ -94,7 +100,8 @@ class ListProtocols(command.Lister): '--identity-provider', metavar='<identity-provider>', required=True, - help='Identity provider to list (name or ID) (required)') + help=_('Identity provider to list (name or ID) (required)'), + ) return parser @@ -118,24 +125,27 @@ class SetProtocol(command.Command): parser.add_argument( 'federation_protocol', metavar='<name>', - help='Federation protocol to modify (name or ID)') + help=_('Federation protocol to modify (name or ID)'), + ) parser.add_argument( '--identity-provider', metavar='<identity-provider>', required=True, - help='Identity provider that supports <federation-protocol> ' - '(name or ID) (required)') + help=_('Identity provider that supports <federation-protocol> ' + '(name or ID) (required)'), + ) parser.add_argument( '--mapping', metavar='<mapping>', - help='Mapping that is to be used (name or ID)') + help=_('Mapping that is to be used (name or ID)'), + ) return parser 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") + self.app.log.error(_("No changes requested")) return protocol = identity_client.federation.protocols.update( @@ -159,13 +169,15 @@ class ShowProtocol(command.ShowOne): parser.add_argument( 'federation_protocol', metavar='<federation-protocol>', - help='Federation protocol to display (name or ID)') + help=_('Federation protocol to display (name or ID)'), + ) parser.add_argument( '--identity-provider', metavar='<identity-provider>', required=True, - help=('Identity provider that supports <federation-protocol> ' - '(name or ID) (required)')) + help=_('Identity provider that supports <federation-protocol> ' + '(name or ID) (required)'), + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 3c24353..fdb94da 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -34,12 +34,12 @@ class AddUserToGroup(command.Command): parser.add_argument( 'group', metavar='<group>', - help='Group to contain <user> (name or ID)', + help=_('Group to contain <user> (name or ID)'), ) parser.add_argument( 'user', metavar='<user>', - help='User to add to <group> (name or ID)', + help=_('User to add to <group> (name or ID)'), ) common.add_group_domain_option_to_parser(parser) common.add_user_domain_option_to_parser(parser) @@ -58,11 +58,17 @@ class AddUserToGroup(command.Command): try: identity_client.users.add_to_group(user_id, group_id) except Exception: - sys.stderr.write("%s not added to group %s\n" % - (parsed_args.user, parsed_args.group)) + msg = _("%(user)s not added to group %(group)s\n") % { + 'user': parsed_args.user, + 'group': parsed_args.group, + } + sys.stderr.write(msg) else: - sys.stdout.write("%s added to group %s\n" % - (parsed_args.user, parsed_args.group)) + msg = _("%(user)s added to group %(group)s\n") % { + 'user': parsed_args.user, + 'group': parsed_args.group, + } + sys.stdout.write(msg) class CheckUserInGroup(command.Command): @@ -73,12 +79,12 @@ class CheckUserInGroup(command.Command): parser.add_argument( 'group', metavar='<group>', - help='Group to check (name or ID)', + help=_('Group to check (name or ID)'), ) parser.add_argument( 'user', metavar='<user>', - help='User to check (name or ID)', + help=_('User to check (name or ID)'), ) common.add_group_domain_option_to_parser(parser) common.add_user_domain_option_to_parser(parser) @@ -97,11 +103,17 @@ class CheckUserInGroup(command.Command): try: identity_client.users.check_in_group(user_id, group_id) except Exception: - sys.stderr.write("%s not in group %s\n" % - (parsed_args.user, parsed_args.group)) + msg = _("%(user)s not in group %(group)s\n") % { + 'user': parsed_args.user, + 'group': parsed_args.group, + } + sys.stderr.write(msg) else: - sys.stdout.write("%s in group %s\n" % - (parsed_args.user, parsed_args.group)) + msg = _("%(user)s in group %(group)s\n") % { + 'user': parsed_args.user, + 'group': parsed_args.group, + } + sys.stdout.write(msg) class CreateGroup(command.ShowOne): @@ -112,17 +124,17 @@ class CreateGroup(command.ShowOne): parser.add_argument( 'name', metavar='<group-name>', - help='New group name', + help=_('New group name'), ) parser.add_argument( '--domain', metavar='<domain>', - help='Domain to contain new group (name or ID)', + help=_('Domain to contain new group (name or ID)'), ) parser.add_argument( '--description', metavar='<description>', - help='New group description', + help=_('New group description'), ) parser.add_argument( '--or-show', @@ -149,7 +161,7 @@ class CreateGroup(command.ShowOne): group = utils.find_resource(identity_client.groups, parsed_args.name, domain_id=domain) - self.log.info('Returning existing group %s', group.name) + self.log.info(_('Returning existing group %s'), group.name) else: raise e @@ -166,11 +178,12 @@ class DeleteGroup(command.Command): 'groups', metavar='<group>', nargs="+", - help='Group(s) to delete (name or ID)') + help=_('Group(s) to delete (name or ID)'), + ) parser.add_argument( '--domain', metavar='<domain>', - help='Domain containing group(s) (name or ID)', + help=_('Domain containing group(s) (name or ID)'), ) return parser @@ -192,19 +205,19 @@ class ListGroup(command.Lister): parser.add_argument( '--domain', metavar='<domain>', - help='Filter group list by <domain> (name or ID)', + help=_('Filter group list by <domain> (name or ID)'), ) parser.add_argument( '--user', metavar='<user>', - help='Filter group list by <user> (name or ID)', + help=_('Filter group list by <user> (name or ID)'), ) common.add_user_domain_option_to_parser(parser) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -252,12 +265,12 @@ class RemoveUserFromGroup(command.Command): parser.add_argument( 'group', metavar='<group>', - help='Group containing <user> (name or ID)', + help=_('Group containing <user> (name or ID)'), ) parser.add_argument( 'user', metavar='<user>', - help='User to remove from <group> (name or ID)', + help=_('User to remove from <group> (name or ID)'), ) common.add_group_domain_option_to_parser(parser) common.add_user_domain_option_to_parser(parser) @@ -276,11 +289,17 @@ class RemoveUserFromGroup(command.Command): try: identity_client.users.remove_from_group(user_id, group_id) except Exception: - sys.stderr.write("%s not removed from group %s\n" % - (parsed_args.user, parsed_args.group)) + msg = _("%(user)s not removed from group %(group)s\n") % { + 'user': parsed_args.user, + 'group': parsed_args.group, + } + sys.stderr.write(msg) else: - sys.stdout.write("%s removed from group %s\n" % - (parsed_args.user, parsed_args.group)) + msg = _("%(user)s removed from group %(group)s\n") % { + 'user': parsed_args.user, + 'group': parsed_args.group, + } + sys.stdout.write(msg) class SetGroup(command.Command): @@ -291,20 +310,23 @@ class SetGroup(command.Command): parser.add_argument( 'group', metavar='<group>', - help='Group to modify (name or ID)') + help=_('Group to modify (name or ID)'), + ) parser.add_argument( '--domain', metavar='<domain>', - help='Domain containing <group> (name or ID)', + help=_('Domain containing <group> (name or ID)'), ) parser.add_argument( '--name', metavar='<name>', - help='New group name') + help=_('New group name'), + ) parser.add_argument( '--description', metavar='<description>', - help='New group description') + help=_('New group description'), + ) return parser def take_action(self, parsed_args): @@ -318,7 +340,7 @@ class SetGroup(command.Command): kwargs['description'] = parsed_args.description if not len(kwargs): - sys.stderr.write("Group not updated, no arguments present") + sys.stderr.write("Group not updated, no arguments present\n") return identity_client.groups.update(group.id, **kwargs) @@ -331,12 +353,12 @@ class ShowGroup(command.ShowOne): parser.add_argument( 'group', metavar='<group>', - help='Group to display (name or ID)', + help=_('Group to display (name or ID)'), ) parser.add_argument( '--domain', metavar='<domain>', - help='Domain containing <group> (name or ID)', + help=_('Domain containing <group> (name or ID)'), ) return parser diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 39f440f..3749aa3 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -17,6 +17,7 @@ import six from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreateIdentityProvider(command.ShowOne): @@ -27,26 +28,26 @@ class CreateIdentityProvider(command.ShowOne): parser.add_argument( 'identity_provider_id', metavar='<name>', - help='New identity provider name (must be unique)' + help=_('New identity provider name (must be unique)'), ) identity_remote_id_provider = parser.add_mutually_exclusive_group() identity_remote_id_provider.add_argument( '--remote-id', metavar='<remote-id>', action='append', - help='Remote IDs to associate with the Identity Provider ' - '(repeat option to provide multiple values)' + help=_('Remote IDs to associate with the Identity Provider ' + '(repeat option to provide multiple values)'), ) identity_remote_id_provider.add_argument( '--remote-id-file', metavar='<file-name>', - help='Name of a file that contains many remote IDs to associate ' - 'with the identity provider, one per line' + help=_('Name of a file that contains many remote IDs to associate ' + 'with the identity provider, one per line'), ) parser.add_argument( '--description', metavar='<description>', - help='New identity provider description', + help=_('New identity provider description'), ) enable_identity_provider = parser.add_mutually_exclusive_group() enable_identity_provider.add_argument( @@ -54,13 +55,13 @@ class CreateIdentityProvider(command.ShowOne): dest='enabled', action='store_true', default=True, - help='Enable identity provider (default)', + help=_('Enable identity provider (default)'), ) enable_identity_provider.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable the identity provider', + help=_('Disable the identity provider'), ) return parser @@ -94,7 +95,7 @@ class DeleteIdentityProvider(command.Command): parser.add_argument( 'identity_provider', metavar='<identity-provider>', - help='Identity provider to delete', + help=_('Identity provider to delete'), ) return parser @@ -126,37 +127,37 @@ class SetIdentityProvider(command.Command): parser.add_argument( 'identity_provider', metavar='<identity-provider>', - help='Identity provider to modify', + help=_('Identity provider to modify'), ) parser.add_argument( '--description', metavar='<description>', - help='Set identity provider description', + help=_('Set identity provider description'), ) identity_remote_id_provider = parser.add_mutually_exclusive_group() identity_remote_id_provider.add_argument( '--remote-id', metavar='<remote-id>', action='append', - help='Remote IDs to associate with the Identity Provider ' - '(repeat option to provide multiple values)' + help=_('Remote IDs to associate with the Identity Provider ' + '(repeat option to provide multiple values)'), ) identity_remote_id_provider.add_argument( '--remote-id-file', metavar='<file-name>', - help='Name of a file that contains many remote IDs to associate ' - 'with the identity provider, one per line' + help=_('Name of a file that contains many remote IDs to associate ' + 'with the identity provider, one per line'), ) enable_identity_provider = parser.add_mutually_exclusive_group() enable_identity_provider.add_argument( '--enable', action='store_true', - help='Enable the identity provider', + help=_('Enable the identity provider'), ) enable_identity_provider.add_argument( '--disable', action='store_true', - help='Disable the identity provider', + help=_('Disable the identity provider'), ) return parser @@ -168,7 +169,7 @@ class SetIdentityProvider(command.Command): not parsed_args.remote_id and not parsed_args.remote_id_file and not parsed_args.description): - self.log.error('No changes requested') + self.log.error(_('No changes requested')) return (None, None) # Always set remote_ids if either is passed in @@ -206,7 +207,7 @@ class ShowIdentityProvider(command.ShowOne): parser.add_argument( 'identity_provider', metavar='<identity-provider>', - help='Identity provider to display', + help=_('Identity provider to display'), ) return parser diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index 3cdc8af..c45796e 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -22,6 +22,7 @@ import six from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.i18n import _ class _RulesReader(object): @@ -69,9 +70,9 @@ class _RulesReader(object): try: rules = json.loads(blob) except ValueError as e: - raise exceptions.CommandError( - 'An error occurred when reading ' - 'rules from file %s: %s' % (path, e)) + msg = _("An error occurred when reading rules from file " + "%(path)s: %(error)s") % {"path": path, "error": e} + raise exceptions.CommandError(msg) else: return rules @@ -84,12 +85,12 @@ class CreateMapping(command.ShowOne, _RulesReader): parser.add_argument( 'mapping', metavar='<name>', - help='New mapping name (must be unique)', + help=_('New mapping name (must be unique)'), ) parser.add_argument( '--rules', metavar='<filename>', required=True, - help='Filename that contains a set of mapping rules (required)', + help=_('Filename that contains a set of mapping rules (required)'), ) return parser @@ -113,7 +114,7 @@ class DeleteMapping(command.Command): parser.add_argument( 'mapping', metavar='<mapping>', - help='Mapping to delete', + help=_('Mapping to delete'), ) return parser @@ -145,12 +146,12 @@ class SetMapping(command.Command, _RulesReader): parser.add_argument( 'mapping', metavar='<name>', - help='Mapping to modify', + help=_('Mapping to modify'), ) parser.add_argument( '--rules', metavar='<filename>', - help='Filename that contains a new set of mapping rules', + help=_('Filename that contains a new set of mapping rules'), ) return parser @@ -158,7 +159,7 @@ class SetMapping(command.Command, _RulesReader): identity_client = self.app.client_manager.identity if not parsed_args.rules: - self.app.log.error("No changes requested") + self.app.log.error(_("No changes requested")) return rules = self._read_rules(parsed_args.rules) @@ -179,7 +180,7 @@ class ShowMapping(command.ShowOne): parser.add_argument( 'mapping', metavar='<mapping>', - help='Mapping to display', + help=_('Mapping to display'), ) return parser diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 3c2d1a7..74a783b 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -20,6 +20,7 @@ import sys from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreatePolicy(command.ShowOne): @@ -31,13 +32,13 @@ class CreatePolicy(command.ShowOne): '--type', metavar='<type>', default="application/json", - help='New MIME type of the policy rules file ' - '(defaults to application/json)', + help=_('New MIME type of the policy rules file ' + '(defaults to application/json)'), ) parser.add_argument( 'rules', metavar='<filename>', - help='New serialized policy rules file', + help=_('New serialized policy rules file'), ) return parser @@ -62,7 +63,7 @@ class DeletePolicy(command.Command): parser.add_argument( 'policy', metavar='<policy>', - help='Policy to delete', + help=_('Policy to delete'), ) return parser @@ -80,7 +81,7 @@ class ListPolicy(command.Lister): '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -107,17 +108,17 @@ class SetPolicy(command.Command): parser.add_argument( 'policy', metavar='<policy>', - help='Policy to modify', + help=_('Policy to modify'), ) parser.add_argument( '--type', metavar='<type>', - help='New MIME type of the policy rules file', + help=_('New MIME type of the policy rules file'), ) parser.add_argument( '--rules', metavar='<filename>', - help='New serialized policy rules file', + help=_('New serialized policy rules file'), ) return parser @@ -135,7 +136,7 @@ class SetPolicy(command.Command): kwargs['type'] = parsed_args.type if not kwargs: - sys.stdout.write('Policy not updated, no arguments present \n') + sys.stdout.write(_('Policy not updated, no arguments present\n')) return identity_client.policies.update(parsed_args.policy, **kwargs) @@ -148,7 +149,7 @@ class ShowPolicy(command.ShowOne): parser.add_argument( 'policy', metavar='<policy>', - help='Policy to display', + help=_('Policy to display'), ) return parser diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 4990b1b..acf639f 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -34,40 +34,40 @@ class CreateProject(command.ShowOne): parser.add_argument( 'name', metavar='<project-name>', - help='New project name', + help=_('New project name'), ) parser.add_argument( '--domain', metavar='<domain>', - help='Domain owning the project (name or ID)', + help=_('Domain owning the project (name or ID)'), ) parser.add_argument( '--parent', metavar='<project>', - help='Parent of the project (name or ID)', + help=_('Parent of the project (name or ID)'), ) parser.add_argument( '--description', metavar='<description>', - help='Project description', + help=_('Project description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable project', + help=_('Enable project'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable project', + help=_('Disable project'), ) parser.add_argument( '--property', metavar='<key=value>', action=parseractions.KeyValueAction, - help='Add a property to <name> ' - '(repeat option to set multiple properties)', + help=_('Add a property to <name> ' + '(repeat option to set multiple properties)'), ) parser.add_argument( '--or-show', @@ -112,7 +112,7 @@ class CreateProject(command.ShowOne): project = utils.find_resource(identity_client.projects, parsed_args.name, domain_id=domain) - self.log.info('Returning existing project %s', project.name) + self.log.info(_('Returning existing project %s'), project.name) else: raise e @@ -129,12 +129,12 @@ class DeleteProject(command.Command): 'projects', metavar='<project>', nargs="+", - help='Project(s) to delete (name or ID)', + help=_('Project(s) to delete (name or ID)'), ) parser.add_argument( '--domain', metavar='<domain>', - help='Domain owning <project> (name or ID)', + help=_('Domain owning <project> (name or ID)'), ) return parser @@ -163,18 +163,18 @@ class ListProject(command.Lister): parser.add_argument( '--domain', metavar='<domain>', - help='Filter projects by <domain> (name or ID)', + help=_('Filter projects by <domain> (name or ID)'), ) parser.add_argument( '--user', metavar='<user>', - help='Filter projects by <user> (name or ID)', + help=_('Filter projects by <user> (name or ID)'), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -219,40 +219,40 @@ class SetProject(command.Command): parser.add_argument( 'project', metavar='<project>', - help='Project to modify (name or ID)', + help=_('Project to modify (name or ID)'), ) parser.add_argument( '--name', metavar='<name>', - help='Set project name', + help=_('Set project name'), ) parser.add_argument( '--domain', metavar='<domain>', - help='Domain owning <project> (name or ID)', + help=_('Domain owning <project> (name or ID)'), ) parser.add_argument( '--description', metavar='<description>', - help='Set project description', + help=_('Set project description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable project', + help=_('Enable project'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable project', + help=_('Disable project'), ) parser.add_argument( '--property', metavar='<key=value>', action=parseractions.KeyValueAction, - help='Set a property on <project> ' - '(repeat option to set multiple properties)', + help=_('Set a property on <project> ' + '(repeat option to set multiple properties)'), ) return parser @@ -293,24 +293,24 @@ class ShowProject(command.ShowOne): parser.add_argument( 'project', metavar='<project>', - help='Project to display (name or ID)', + help=_('Project to display (name or ID)'), ) parser.add_argument( '--domain', metavar='<domain>', - help='Domain owning <project> (name or ID)', + help=_('Domain owning <project> (name or ID)'), ) parser.add_argument( '--parents', action='store_true', default=False, - help='Show the project\'s parents as a list', + help=_('Show the project\'s parents as a list'), ) parser.add_argument( '--children', action='store_true', default=False, - help='Show project\'s subtree (children) as a list', + help=_('Show project\'s subtree (children) as a list'), ) return parser diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index f93c9d8..e7078f4 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -31,23 +31,23 @@ def _add_identity_and_resource_options_to_parser(parser): domain_or_project.add_argument( '--domain', metavar='<domain>', - help='Include <domain> (name or ID)', + help=_('Include <domain> (name or ID)'), ) domain_or_project.add_argument( '--project', metavar='<project>', - help='Include <project> (name or ID)', + help=_('Include <project> (name or ID)'), ) user_or_group = parser.add_mutually_exclusive_group() user_or_group.add_argument( '--user', metavar='<user>', - help='Include <user> (name or ID)', + help=_('Include <user> (name or ID)'), ) user_or_group.add_argument( '--group', metavar='<group>', - help='Include <group> (name or ID)', + help=_('Include <group> (name or ID)'), ) common.add_group_domain_option_to_parser(parser) common.add_project_domain_option_to_parser(parser) @@ -112,7 +112,7 @@ class AddRole(command.Command): parser.add_argument( 'role', metavar='<role>', - help='Role to add to <user> (name or ID)', + help=_('Role to add to <user> (name or ID)'), ) _add_identity_and_resource_options_to_parser(parser) return parser @@ -131,9 +131,9 @@ class AddRole(command.Command): kwargs = _process_identity_and_resource_options( parsed_args, self.app.client_manager.identity) if not kwargs: - sys.stderr.write("Role not added, incorrect set of arguments " - "provided. See openstack --help for more " - "details\n") + sys.stderr.write(_("Role not added, incorrect set of arguments " + "provided. See openstack --help for more " + "details\n")) return identity_client.roles.grant(role.id, **kwargs) @@ -147,7 +147,7 @@ class CreateRole(command.ShowOne): parser.add_argument( 'name', metavar='<role-name>', - help='New role name', + help=_('New role name'), ) parser.add_argument( '--or-show', @@ -165,7 +165,7 @@ class CreateRole(command.ShowOne): if parsed_args.or_show: role = utils.find_resource(identity_client.roles, parsed_args.name) - self.log.info('Returning existing role %s', role.name) + self.log.info(_('Returning existing role %s'), role.name) else: raise e @@ -182,7 +182,7 @@ class DeleteRole(command.Command): 'roles', metavar='<role>', nargs="+", - help='Role(s) to delete (name or ID)', + help=_('Role(s) to delete (name or ID)'), ) return parser @@ -285,9 +285,9 @@ class ListRole(command.Lister): group_role.group = group.name group_role.project = project.name else: - sys.stderr.write("Error: If a user or group is specified, either " - "--domain or --project must also be specified to " - "list role grants.\n") + sys.stderr.write(_("Error: If a user or group is specified, " + "either --domain or --project must also be " + "specified to list role grants.\n")) return ([], []) return (columns, @@ -305,7 +305,7 @@ class RemoveRole(command.Command): parser.add_argument( 'role', metavar='<role>', - help='Role to remove (name or ID)', + help=_('Role to remove (name or ID)'), ) _add_identity_and_resource_options_to_parser(parser) return parser @@ -315,9 +315,8 @@ class RemoveRole(command.Command): if (not parsed_args.user and not parsed_args.domain and not parsed_args.group and not parsed_args.project): - sys.stderr.write("Incorrect set of arguments " - "provided. See openstack --help for more " - "details\n") + sys.stderr.write(_("Incorrect set of arguments provided. " + "See openstack --help for more details\n")) return role = utils.find_resource( identity_client.roles, @@ -327,8 +326,9 @@ class RemoveRole(command.Command): kwargs = _process_identity_and_resource_options( parsed_args, self.app.client_manager.identity) if not kwargs: - sys.stderr.write("Role not removed, incorrect set of arguments \ - provided. See openstack --help for more details\n") + sys.stderr.write(_("Role not removed, incorrect set of arguments " + "provided. See openstack --help for more " + "details\n")) return identity_client.roles.revoke(role.id, **kwargs) @@ -341,12 +341,12 @@ class SetRole(command.Command): parser.add_argument( 'role', metavar='<role>', - help='Role to modify (name or ID)', + help=_('Role to modify (name or ID)'), ) parser.add_argument( '--name', metavar='<name>', - help='Set role name', + help=_('Set role name'), ) return parser @@ -354,9 +354,8 @@ class SetRole(command.Command): 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") + sys.stderr.write(_("Incorrect set of arguments provided. " + "See openstack --help for more details\n")) return role = utils.find_resource( identity_client.roles, @@ -374,7 +373,7 @@ class ShowRole(command.ShowOne): parser.add_argument( 'role', metavar='<role>', - help='Role to display (name or ID)', + help=_('Role to display (name or ID)'), ) return parser diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index a1418a8..521075f 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -15,6 +15,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common @@ -27,41 +28,41 @@ class ListRoleAssignment(command.Lister): '--effective', action="store_true", default=False, - help='Returns only effective role assignments', + help=_('Returns only effective role assignments'), ) parser.add_argument( '--role', metavar='<role>', - help='Role to filter (name or ID)', + help=_('Role to filter (name or ID)'), ) parser.add_argument( '--names', action="store_true", - help='Display names instead of IDs', + help=_('Display names instead of IDs'), ) user_or_group = parser.add_mutually_exclusive_group() user_or_group.add_argument( '--user', metavar='<user>', - help='User to filter (name or ID)', + help=_('User to filter (name or ID)'), ) common.add_user_domain_option_to_parser(parser) user_or_group.add_argument( '--group', metavar='<group>', - help='Group to filter (name or ID)', + help=_('Group to filter (name or ID)'), ) common.add_group_domain_option_to_parser(parser) domain_or_project = parser.add_mutually_exclusive_group() domain_or_project.add_argument( '--domain', metavar='<domain>', - help='Domain to filter (name or ID)', + help=_('Domain to filter (name or ID)'), ) domain_or_project.add_argument( '--project', metavar='<project>', - help='Project to filter (name or ID)', + help=_('Project to filter (name or ID)'), ) common.add_project_domain_option_to_parser(parser) common.add_inherited_option_to_parser(parser) diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index f43ada5..35507a6 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -20,6 +20,7 @@ import sys from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common @@ -31,28 +32,28 @@ class CreateService(command.ShowOne): parser.add_argument( 'type', metavar='<type>', - help='New service type (compute, image, identity, volume, etc)', + help=_('New service type (compute, image, identity, volume, etc)'), ) parser.add_argument( '--name', metavar='<name>', - help='New service name', + help=_('New service name'), ) parser.add_argument( '--description', metavar='<description>', - help='New service description', + help=_('New service description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable service (default)', + help=_('Enable service (default)'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable service', + help=_('Disable service'), ) return parser @@ -82,7 +83,7 @@ class DeleteService(command.Command): parser.add_argument( 'service', metavar='<service>', - help='Service to delete (type, name or ID)', + help=_('Service to delete (type, name or ID)'), ) return parser @@ -103,7 +104,7 @@ class ListService(command.Lister): '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -128,33 +129,33 @@ class SetService(command.Command): parser.add_argument( 'service', metavar='<service>', - help='Service to update (type, name or ID)', + help=_('Service to update (type, name or ID)'), ) parser.add_argument( '--type', metavar='<type>', - help='New service type (compute, image, identity, volume, etc)', + help=_('New service type (compute, image, identity, volume, etc)'), ) parser.add_argument( '--name', metavar='<service-name>', - help='New service name', + help=_('New service name'), ) parser.add_argument( '--description', metavar='<description>', - help='New service description', + help=_('New service description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable service', + help=_('Enable service'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable service', + help=_('Disable service'), ) return parser @@ -166,9 +167,8 @@ class SetService(command.Command): 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") + 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) @@ -198,7 +198,7 @@ class ShowService(command.ShowOne): parser.add_argument( 'service', metavar='<service>', - help='Service to display (type, name or ID)', + help=_('Service to display (type, name or ID)'), ) return parser diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 8b433b4..f1e9f35 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -18,6 +18,7 @@ import sys from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreateServiceProvider(command.ShowOne): @@ -28,26 +29,26 @@ class CreateServiceProvider(command.ShowOne): parser.add_argument( 'service_provider_id', metavar='<name>', - help='New service provider name (must be unique)' + help=_('New service provider name (must be unique)'), ) parser.add_argument( '--auth-url', metavar='<auth-url>', required=True, - help='Authentication URL of remote federated service provider ' - '(required)', + help=_('Authentication URL of remote federated service provider ' + '(required)'), ) parser.add_argument( '--description', metavar='<description>', - help='New service provider description', + help=_('New service provider description'), ) parser.add_argument( '--service-provider-url', metavar='<sp-url>', required=True, - help='A service URL where SAML assertions are being sent ' - '(required)', + help=_('A service URL where SAML assertions are being sent ' + '(required)'), ) enable_service_provider = parser.add_mutually_exclusive_group() @@ -56,13 +57,13 @@ class CreateServiceProvider(command.ShowOne): dest='enabled', action='store_true', default=True, - help='Enable the service provider (default)', + help=_('Enable the service provider (default)'), ) enable_service_provider.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable the service provider', + help=_('Disable the service provider'), ) return parser @@ -88,7 +89,7 @@ class DeleteServiceProvider(command.Command): parser.add_argument( 'service_provider', metavar='<service-provider>', - help='Service provider to delete', + help=_('Service provider to delete'), ) return parser @@ -121,34 +122,35 @@ class SetServiceProvider(command.Command): parser.add_argument( 'service_provider', metavar='<service-provider>', - help='Service provider to modify', + help=_('Service provider to modify'), ) parser.add_argument( '--auth-url', metavar='<auth-url>', - help='New Authentication URL of remote federated service provider', + help=_('New Authentication URL of remote ' + 'federated service provider'), ) parser.add_argument( '--description', metavar='<description>', - help='New service provider description', + help=_('New service provider description'), ) parser.add_argument( '--service-provider-url', metavar='<sp-url>', - help='New service provider URL, where SAML assertions are sent', + help=_('New service provider URL, where SAML assertions are sent'), ) enable_service_provider = parser.add_mutually_exclusive_group() enable_service_provider.add_argument( '--enable', action='store_true', - help='Enable the service provider', + help=_('Enable the service provider'), ) enable_service_provider.add_argument( '--disable', action='store_true', - help='Disable the service provider', + help=_('Disable the service provider'), ) return parser @@ -164,8 +166,8 @@ class SetServiceProvider(command.Command): 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") + sys.stdout.write(_("Service Provider not updated, no arguments " + "present\n")) return (None, None) service_provider = federation_client.service_providers.update( @@ -184,7 +186,7 @@ class ShowServiceProvider(command.ShowOne): parser.add_argument( 'service_provider', metavar='<service-provider>', - help='Service provider to display', + help=_('Service provider to display'), ) return parser diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index bdc5e95..56a7497 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -20,6 +20,7 @@ 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 @@ -31,17 +32,17 @@ class AuthorizeRequestToken(command.ShowOne): parser.add_argument( '--request-key', metavar='<request-key>', - help='Request token to authorize (ID only) (required)', - required=True + required=True, + help=_('Request token to authorize (ID only) (required)'), ) parser.add_argument( '--role', metavar='<role>', action='append', default=[], - help='Roles to authorize (name or ID) ' - '(repeat option to set multiple values, required)', - required=True + required=True, + help=_('Roles to authorize (name or ID) ' + '(repeat option to set multiple values, required)'), ) return parser @@ -72,31 +73,31 @@ class CreateAccessToken(command.ShowOne): parser.add_argument( '--consumer-key', metavar='<consumer-key>', - help='Consumer key (required)', + help=_('Consumer key (required)'), required=True ) parser.add_argument( '--consumer-secret', metavar='<consumer-secret>', - help='Consumer secret (required)', + help=_('Consumer secret (required)'), required=True ) parser.add_argument( '--request-key', metavar='<request-key>', - help='Request token to exchange for access token (required)', + help=_('Request token to exchange for access token (required)'), required=True ) parser.add_argument( '--request-secret', metavar='<request-secret>', - help='Secret associated with <request-key> (required)', + help=_('Secret associated with <request-key> (required)'), required=True ) parser.add_argument( '--verifier', metavar='<verifier>', - help='Verifier associated with <request-key> (required)', + help=_('Verifier associated with <request-key> (required)'), required=True ) return parser @@ -118,26 +119,26 @@ class CreateRequestToken(command.ShowOne): parser.add_argument( '--consumer-key', metavar='<consumer-key>', - help='Consumer key (required)', + help=_('Consumer key (required)'), required=True ) parser.add_argument( '--consumer-secret', metavar='<consumer-secret>', - help='Consumer secret (required)', + help=_('Consumer secret (required)'), required=True ) parser.add_argument( '--project', metavar='<project>', - help='Project that consumer wants to access (name or ID)' - ' (required)', + help=_('Project that consumer wants to access (name or ID)' + ' (required)'), required=True ) parser.add_argument( '--domain', metavar='<domain>', - help='Domain owning <project> (name or ID)', + help=_('Domain owning <project> (name or ID)'), ) return parser @@ -175,7 +176,7 @@ class IssueToken(command.ShowOne): def take_action(self, parsed_args): if not self.app.client_manager.auth_ref: raise exceptions.AuthorizationFailure( - "Only an authorized user may issue a new token.") + _("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') @@ -190,7 +191,7 @@ class RevokeToken(command.Command): parser.add_argument( 'token', metavar='<token>', - help='Token to be deleted', + help=_('Token to be deleted'), ) return parser diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index b6f5c6b..336b703 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -18,6 +18,7 @@ import six from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common @@ -29,26 +30,26 @@ class CreateTrust(command.ShowOne): parser.add_argument( 'trustor', metavar='<trustor-user>', - help='User that is delegating authorization (name or ID)', + help=_('User that is delegating authorization (name or ID)'), ) parser.add_argument( 'trustee', metavar='<trustee-user>', - help='User that is assuming authorization (name or ID)', + help=_('User that is assuming authorization (name or ID)'), ) parser.add_argument( '--project', metavar='<project>', required=True, - help='Project being delegated (name or ID) (required)', + help=_('Project being delegated (name or ID) (required)'), ) parser.add_argument( '--role', metavar='<role>', action='append', default=[], - help='Roles to authorize (name or ID) ' - '(repeat option to set multiple values, required)', + help=_('Roles to authorize (name or ID) ' + '(repeat option to set multiple values, required)'), required=True ) parser.add_argument( @@ -56,25 +57,25 @@ class CreateTrust(command.ShowOne): dest='impersonate', action='store_true', default=False, - help='Tokens generated from the trust will represent <trustor>' - ' (defaults to False)', + help=_('Tokens generated from the trust will represent <trustor>' + ' (defaults to False)'), ) parser.add_argument( '--expiration', metavar='<expiration>', - help='Sets an expiration date for the trust' - ' (format of YYYY-mm-ddTHH:MM:SS)', + help=_('Sets an expiration date for the trust' + ' (format of YYYY-mm-ddTHH:MM:SS)'), ) common.add_project_domain_option_to_parser(parser) parser.add_argument( '--trustor-domain', metavar='<trustor-domain>', - help='Domain that contains <trustor> (name or ID)', + help=_('Domain that contains <trustor> (name or ID)'), ) parser.add_argument( '--trustee-domain', metavar='<trustee-domain>', - help='Domain that contains <trustee> (name or ID)', + help=_('Domain that contains <trustee> (name or ID)'), ) return parser @@ -136,8 +137,8 @@ class DeleteTrust(command.Command): parser.add_argument( 'trust', metavar='<trust>', - help='Trust(s) to delete', nargs="+", + help=_('Trust(s) to delete'), ) return parser @@ -170,7 +171,7 @@ class ShowTrust(command.ShowOne): parser.add_argument( 'trust', metavar='<trust>', - help='Trust to display', + help=_('Trust to display'), ) return parser diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index 8e2616a..5cb8e48 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -20,6 +20,7 @@ a scoped token.""" from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.i18n import _ UNSCOPED_AUTH_PLUGINS = ['v3unscopedsaml', 'v3unscopedadfs', 'v3oidc'] @@ -33,10 +34,10 @@ def auth_with_unscoped_saml(func): if auth_plugin_name in UNSCOPED_AUTH_PLUGINS: return func(self, parsed_args) else: - msg = ('This command requires the use of an unscoped SAML ' - 'authentication plugin. Please use argument ' - '--os-auth-type with one of the following ' - 'plugins: ' + ', '.join(UNSCOPED_AUTH_PLUGINS)) + msg = (_('This command requires the use of an unscoped SAML ' + 'authentication plugin. Please use argument ' + '--os-auth-type with one of the following ' + 'plugins: %s') % ', '.join(UNSCOPED_AUTH_PLUGINS)) raise exceptions.CommandError(msg) return _decorated diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 9a7ced9..a71b4b7 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -35,50 +35,50 @@ class CreateUser(command.ShowOne): parser.add_argument( 'name', metavar='<name>', - help='New user name', + help=_('New user name'), ) parser.add_argument( '--domain', metavar='<domain>', - help='Default domain (name or ID)', + help=_('Default domain (name or ID)'), ) parser.add_argument( '--project', metavar='<project>', - help='Default project (name or ID)', + help=_('Default project (name or ID)'), ) common.add_project_domain_option_to_parser(parser) parser.add_argument( '--password', metavar='<password>', - help='Set user password', + help=_('Set user password'), ) parser.add_argument( '--password-prompt', dest="password_prompt", action="store_true", - help='Prompt interactively for password', + help=_('Prompt interactively for password'), ) parser.add_argument( '--email', metavar='<email-address>', - help='Set user email address', + help=_('Set user email address'), ) parser.add_argument( '--description', metavar='<description>', - help='User description', + help=_('User description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable user (default)', + help=_('Enable user (default)'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable user', + help=_('Disable user'), ) parser.add_argument( '--or-show', @@ -122,7 +122,7 @@ class CreateUser(command.ShowOne): user = utils.find_resource(identity_client.users, parsed_args.name, domain_id=domain_id) - self.log.info('Returning existing user %s', user.name) + self.log.info(_('Returning existing user %s'), user.name) else: raise e @@ -139,12 +139,12 @@ class DeleteUser(command.Command): 'users', metavar='<user>', nargs="+", - help='User(s) to delete (name or ID)', + help=_('User(s) to delete (name or ID)'), ) parser.add_argument( '--domain', metavar='<domain>', - help='Domain owning <user> (name or ID)', + help=_('Domain owning <user> (name or ID)'), ) return parser @@ -173,24 +173,24 @@ class ListUser(command.Lister): parser.add_argument( '--domain', metavar='<domain>', - help='Filter users by <domain> (name or ID)', + help=_('Filter users by <domain> (name or ID)'), ) project_or_group = parser.add_mutually_exclusive_group() project_or_group.add_argument( '--group', metavar='<group>', - help='Filter users by <group> membership (name or ID)', + help=_('Filter users by <group> membership (name or ID)'), ) project_or_group.add_argument( '--project', metavar='<project>', - help='Filter users by <project> (name or ID)', + help=_('Filter users by <project> (name or ID)'), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -273,50 +273,50 @@ class SetUser(command.Command): parser.add_argument( 'user', metavar='<user>', - help='User to change (name or ID)', + help=_('User to change (name or ID)'), ) parser.add_argument( '--name', metavar='<name>', - help='Set user name', + help=_('Set user name'), ) parser.add_argument( '--project', metavar='<project>', - help='Set default project (name or ID)', + help=_('Set default project (name or ID)'), ) common.add_project_domain_option_to_parser(parser) parser.add_argument( '--password', metavar='<password>', - help='Set user password', + help=_('Set user password'), ) parser.add_argument( '--password-prompt', dest="password_prompt", action="store_true", - help='Prompt interactively for password', + help=_('Prompt interactively for password'), ) parser.add_argument( '--email', metavar='<email-address>', - help='Set user email address', + help=_('Set user email address'), ) parser.add_argument( '--description', metavar='<description>', - help='Set user description', + help=_('Set user description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable user (default)', + help=_('Enable user (default)'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable user', + help=_('Disable user'), ) return parser @@ -334,9 +334,8 @@ class SetUser(command.Command): 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") + sys.stderr.write(_("Incorrect set of arguments provided. " + "See openstack --help for more details\n")) return user = utils.find_resource( @@ -369,17 +368,19 @@ class SetUser(command.Command): class SetPasswordUser(command.Command): """Change current user password""" + required_scope = False + def get_parser(self, prog_name): parser = super(SetPasswordUser, self).get_parser(prog_name) parser.add_argument( '--password', metavar='<new-password>', - help='New user password' + help=_('New user password'), ) parser.add_argument( '--original-password', metavar='<original-password>', - help='Original user password' + help=_('Original user password'), ) return parser @@ -427,12 +428,12 @@ class ShowUser(command.ShowOne): parser.add_argument( 'user', metavar='<user>', - help='User to display (name or ID)', + help=_('User to display (name or ID)'), ) parser.add_argument( '--domain', metavar='<domain>', - help='Domain owning <user> (name or ID)', + help=_('Domain owning <user> (name or ID)'), ) return parser diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index a9c0f1f..fa1de42 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -805,8 +805,8 @@ class SetImage(command.Command): # Checks if anything that requires getting the image if not (kwargs or parsed_args.deactivate or parsed_args.activate): - self.log.warning(_("No arguments specified")) - return {}, {} + msg = _("No arguments specified") + raise exceptions.CommandError(msg) image = utils.find_resource( image_client.images, parsed_args.image) @@ -856,3 +856,88 @@ class ShowImage(command.ShowOne): info = _format_image(image) return zip(*sorted(six.iteritems(info))) + + +class UnsetImage(command.Command): + """Unset image tags and properties""" + + def get_parser(self, prog_name): + parser = super(UnsetImage, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="<image>", + help=_("Image to modify (name or ID)"), + ) + parser.add_argument( + "--tag", + dest="tags", + metavar="<tag>", + default=[], + action='append', + help=_("Unset a tag on this image " + "(repeat option to set multiple tags)"), + ) + parser.add_argument( + "--property", + dest="properties", + metavar="<property_key>", + default=[], + action='append', + help=_("Unset a property on this image " + "(repeat option to set multiple properties)"), + ) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + 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 + if parsed_args.tags: + for k in parsed_args.tags: + try: + image_client.image_tags.delete(image.id, k) + except Exception: + self.log.error(_("tag unset failed," + " '%s' is a nonexistent tag ") % k) + tagret += 1 + + if parsed_args.properties: + for k in parsed_args.properties: + try: + assert(k in image.keys()) + except AssertionError: + self.log.error(_("property unset failed," + " '%s' is a nonexistent property ") % k) + propret += 1 + image_client.images.update( + image.id, + parsed_args.properties, + **kwargs) + + tagtotal = len(parsed_args.tags) + proptotal = len(parsed_args.properties) + if (tagret > 0 and propret > 0): + msg = (_("Failed to unset %(tagret)s of %(tagtotal)s tags," + "Failed to unset %(propret)s of %(proptotal)s properties.") + % {'tagret': tagret, 'tagtotal': tagtotal, + 'propret': propret, 'proptotal': proptotal}) + raise exceptions.CommandError(msg) + elif tagret > 0: + msg = (_("Failed to unset %(tagret)s of %(tagtotal)s tags.") + % {'tagret': tagret, 'tagtotal': tagtotal}) + raise exceptions.CommandError(msg) + elif propret > 0: + msg = (_("Failed to unset %(propret)s of %(proptotal)s" + " properties.") + % {'propret': propret, 'proptotal': proptotal}) + raise exceptions.CommandError(msg) diff --git a/openstackclient/locale/de/LC_MESSAGES/openstackclient.po b/openstackclient/locale/de/LC_MESSAGES/openstackclient.po index f7445b1..06f1c8d 100644 --- a/openstackclient/locale/de/LC_MESSAGES/openstackclient.po +++ b/openstackclient/locale/de/LC_MESSAGES/openstackclient.po @@ -8,13 +8,13 @@ # Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 2.2.1.dev235\n" +"Project-Id-Version: python-openstackclient 2.5.1.dev51\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-19 05:02+0000\n" +"POT-Creation-Date: 2016-06-03 19:37+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2015-09-19 07:10+0000\n" +"PO-Revision-Date: 2016-06-02 01:43+0000\n" "Last-Translator: Andreas Jaeger <jaegerandi@gmail.com>\n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" @@ -22,69 +22,6 @@ msgstr "" "X-Generator: Zanata 3.7.3\n" "Language-Team: German\n" -msgid "" -"\n" -"Complete\n" -msgstr "" -"\n" -"Fertig\n" - -msgid "" -"\n" -"Error creating server" -msgstr "" -"\n" -"Fehler beim Erstellen des Servers" - -msgid "" -"\n" -"Error creating server snapshot" -msgstr "" -"\n" -"Fehler beim Erstellen der Server-Schattenkopie" - -msgid "" -"\n" -"Error deleting server" -msgstr "" -"\n" -"Fehler beim Löschen des Servers" - -msgid "" -"\n" -"Error migrating server" -msgstr "" -"\n" -"Fehler beim Migrieren des Servers" - -msgid "" -"\n" -"Error rebooting server\n" -msgstr "" -"\n" -"Fehler beim Neustarten des Servers\n" - -msgid "" -"\n" -"Error rebuilding server" -msgstr "" -"\n" -"Fehler bei der Wiederherstellung des Servers" - -msgid "" -"\n" -"Error resizing server" -msgstr "" -"\n" -"Fehler bei der Größenänderung des Servers" - -msgid "" -"\n" -"Reboot complete\n" -msgstr "" -"\n" -"Neustart abgeschlossen\n" - msgid "Add a property to <name> (repeat option to set multiple properties)" msgstr "" "Fügen Sie eine Eigenschaft zu <Name> hinzu (wiederholen Sie die Option, um " @@ -93,6 +30,9 @@ msgstr "" msgid "Allow disk over-commit on the destination host" msgstr "Festplattenüberladung auf dem Zielhost erlauben" +msgid "Availability zone name" +msgstr "Name der Verfügbarkeitszone" + msgid "Complete\n" msgstr "Fertig\n" @@ -138,6 +78,9 @@ msgstr "Anmeldedaten für Benutzer löschen (name or ID)" msgid "Destination port (ssh -p option)" msgstr "Zielport (ssh -p Option)" +msgid "Disable a service" +msgstr "Dienst deaktivieren" + msgid "Disable project" msgstr "Projekt deaktivieren" @@ -162,13 +105,19 @@ msgstr "Benutzer aktivieren (Standardeinstellung)" msgid "Endpoint ID to delete" msgstr "Zu löschende Endpunktkennung" -msgid "Endpoint ID to display" -msgstr "Anzuzeigende Endpunktkennung" +msgid "Error creating server\n" +msgstr "Fehler beim Erstellen des Servers\n" + +msgid "Error creating server snapshot\n" +msgstr "Fehler beim Erstellen der Server-Schattenkopie\n" #, python-format msgid "Error creating server: %s" msgstr "Fehler beim Erstellen des Servers: %s" +msgid "Error deleting server\n" +msgstr "Fehler beim Löschen des Servers\n" + #, python-format msgid "Error deleting server: %s" msgstr "Fehler beim Löschen des Servers: %s" @@ -188,6 +137,9 @@ msgstr "Benutzer nach <project> filtern (Name oder Kennung)" msgid "Filter users by project (name or ID)" msgstr "Benutzer nach Projekt filtern (Name oder Kennung)" +msgid "Floating IP address" +msgstr "Bewegliche IP-Adresse" + msgid "Hints for the scheduler (optional extension)" msgstr "Hinweise für den Planer (optionale Erweiterung)" @@ -209,6 +161,9 @@ msgstr "Zusätzliche Felder in der Ausgabe auflisten" msgid "Login name (ssh -l option)" msgstr "Anmeldename (ssh -l Option)" +msgid "MD5 hash" +msgstr "MD5-Hashwert" + msgid "" "Map block devices; map is <id>:<type>:<size(GB)>:<delete_on_terminate> " "(optional extension)" @@ -442,27 +397,27 @@ msgstr "" "OS_DOMAIN_NAME oder auth.domain_name." msgid "" -"Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" +"Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url" msgstr "" "Legen Sie eine Dienst-AUTH_URL mit --os-auth-url, OS_AUTH_URL oder auth." -"auth_url fest\n" +"auth_url fest" -msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" -msgstr "Legen Sie eine Dienst-URL mit --os-url, OS_URL oder auth.url fest\n" +msgid "Set a service URL, with --os-url, OS_URL or auth.url" +msgstr "Legen Sie eine Dienst-URL mit --os-url, OS_URL oder auth.url fest" -msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" -msgstr "Legen Sie einen Token mit --os-token, OS_TOKEN oder auth.token fest\n" +msgid "Set a token with --os-token, OS_TOKEN or auth.token" +msgstr "Legen Sie einen Token mit --os-token, OS_TOKEN oder auth.token fest" -msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" +msgid "Set a username with --os-username, OS_USERNAME, or auth.username" msgstr "" "Legen Sie einen Benutzernamen mit --os-username, OS_USERNAME oder auth." -"username fest\n" +"username fest" msgid "" -"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" +"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url" msgstr "" "Legen Sie eine Authentifizierungs-URL mit --os-auth-url, OS_AUTH_URL oder " -"auth.auth_url fest\n" +"auth.auth_url fest" msgid "Set default project (name or ID)" msgstr "Standardprojekt festlegen (Name oder Kennung)" @@ -504,6 +459,9 @@ msgstr "" msgid "Token to be deleted" msgstr "Zu löschender Token" +msgid "URL" +msgstr "URL" + msgid "Use only IPv4 addresses" msgstr "Nur IPv4-Adressen verwenden" @@ -543,6 +501,9 @@ msgstr "Aufzulistender Benutzer (Name oder Kennung)" msgid "User(s) to delete (name or ID)" msgstr "Zu löschende(r) Benutzer (Name oder Kennung)" +msgid "Version" +msgstr "Version" + msgid "Volume to add (name or ID)" msgstr "Zu hinzufügender Datenträger (Name oder Kennung)" diff --git a/openstackclient/locale/openstackclient.pot b/openstackclient/locale/openstackclient.pot deleted file mode 100644 index e197fa7..0000000 --- a/openstackclient/locale/openstackclient.pot +++ /dev/null @@ -1,1410 +0,0 @@ -# Translations template for python-openstackclient. -# Copyright (C) 2016 ORGANIZATION -# This file is distributed under the same license as the -# python-openstackclient project. -# FIRST AUTHOR <EMAIL@ADDRESS>, 2016. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: python-openstackclient 2.2.1.dev235\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-19 06:14+0000\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.2.0\n" - -#: openstackclient/api/auth.py:148 -msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" -msgstr "" - -#: openstackclient/api/auth.py:151 -msgid "" -"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or " -"auth.auth_url\n" -msgstr "" - -#: openstackclient/api/auth.py:160 -msgid "" -"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" -msgstr "" - -#: openstackclient/api/auth.py:166 openstackclient/api/auth.py:172 -msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" -msgstr "" - -#: openstackclient/api/auth.py:168 -msgid "Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" -msgstr "" - -#: openstackclient/api/auth.py:174 -msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" -msgstr "" - -#: openstackclient/common/availability_zone.py:110 -#: openstackclient/compute/v2/server.py:757 -#: openstackclient/identity/v2_0/endpoint.py:101 -#: openstackclient/identity/v2_0/project.py:133 -#: openstackclient/identity/v2_0/service.py:115 -#: openstackclient/identity/v2_0/user.py:162 -#: openstackclient/network/v2/router.py:230 -#: openstackclient/network/v2/subnet.py:299 -#: openstackclient/network/v2/subnet_pool.py:165 -msgid "List additional fields in output" -msgstr "" - -#: openstackclient/common/parseractions.py:99 -#, python-format -msgid "" -"Invalid keys %(invalid_keys)s specified.\n" -"Valid keys are: %(valid_keys)s." -msgstr "" - -#: openstackclient/common/parseractions.py:109 -#, python-format -msgid "" -"Missing required keys %(missing_keys)s.\n" -"Required keys are: %(required_keys)s." -msgstr "" - -#: openstackclient/compute/v2/server.py:195 -#: openstackclient/compute/v2/server.py:227 -#: openstackclient/compute/v2/server.py:598 -#: openstackclient/compute/v2/server.py:937 -#: openstackclient/compute/v2/server.py:1039 -#: openstackclient/compute/v2/server.py:1091 -#: openstackclient/compute/v2/server.py:1177 -#: openstackclient/compute/v2/server.py:1213 -#: openstackclient/compute/v2/server.py:1236 -#: openstackclient/compute/v2/server.py:1343 -#: openstackclient/compute/v2/server.py:1421 -#: openstackclient/compute/v2/server.py:1455 -#: openstackclient/compute/v2/server.py:1712 -#: openstackclient/compute/v2/server.py:1733 -msgid "Server (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:200 -msgid "Security group to add (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:232 -msgid "Volume to add (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:237 -msgid "Server internal device name for volume" -msgstr "" - -#: openstackclient/compute/v2/server.py:269 -#: openstackclient/compute/v2/server.py:1348 -msgid "New server name" -msgstr "" - -#: openstackclient/compute/v2/server.py:277 -msgid "Create server from this image (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:282 -msgid "Create server from this volume (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:288 -msgid "Create server with this flavor (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:295 -msgid "" -"Security group to assign to this server (name or ID) (repeat option to " -"set multiple groups)" -msgstr "" - -#: openstackclient/compute/v2/server.py:301 -msgid "Keypair to inject into this server (optional extension)" -msgstr "" - -#: openstackclient/compute/v2/server.py:307 -msgid "Set a property on this server (repeat option to set multiple values)" -msgstr "" - -#: openstackclient/compute/v2/server.py:315 -msgid "" -"File to inject into image before boot (repeat option to set multiple " -"files)" -msgstr "" - -#: openstackclient/compute/v2/server.py:321 -msgid "User data file to serve from the metadata server" -msgstr "" - -#: openstackclient/compute/v2/server.py:326 -msgid "Select an availability zone for the server" -msgstr "" - -#: openstackclient/compute/v2/server.py:333 -msgid "" -"Map block devices; map is <id>:<type>:<size(GB)>:<delete_on_terminate> " -"(optional extension)" -msgstr "" - -#: openstackclient/compute/v2/server.py:343 -msgid "" -"Create a NIC on the server. Specify option multiple times to create " -"multiple NICs. Either net-id or port-id must be provided, but not both. " -"net-id: attach NIC to network with this UUID, port-id: attach NIC to port" -" with this UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6" -"-fixed-ip: IPv6 fixed address for NIC (optional)." -msgstr "" - -#: openstackclient/compute/v2/server.py:356 -msgid "Hints for the scheduler (optional extension)" -msgstr "" - -#: openstackclient/compute/v2/server.py:362 -msgid "" -"Use specified volume as the config drive, or 'True' to use an ephemeral " -"drive" -msgstr "" - -#: openstackclient/compute/v2/server.py:370 -msgid "Minimum number of servers to launch (default=1)" -msgstr "" - -#: openstackclient/compute/v2/server.py:377 -msgid "Maximum number of servers to launch (default=1)" -msgstr "" - -#: openstackclient/compute/v2/server.py:382 -msgid "Wait for build to complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:421 -msgid "min instances should be <= max instances" -msgstr "" - -#: openstackclient/compute/v2/server.py:424 -msgid "min instances should be > 0" -msgstr "" - -#: openstackclient/compute/v2/server.py:427 -msgid "max instances should be > 0" -msgstr "" - -#: openstackclient/compute/v2/server.py:456 -msgid "Volume name or ID must be specified if --block-device-mapping is specified" -msgstr "" - -#: openstackclient/compute/v2/server.py:468 -msgid "either net-id or port-id should be specified but not both" -msgstr "" - -#: openstackclient/compute/v2/server.py:488 -msgid "can't create server with port specified since network endpoint not enabled" -msgstr "" - -#: openstackclient/compute/v2/server.py:553 -#, python-format -msgid "Error creating server: %s" -msgstr "" - -#: openstackclient/compute/v2/server.py:555 -msgid "" -"\n" -"Error creating server" -msgstr "" - -#: openstackclient/compute/v2/server.py:577 -msgid "Server(s) to create dump file (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:603 -msgid "Name of new image (default is server name)" -msgstr "" - -#: openstackclient/compute/v2/server.py:608 -msgid "Wait for image create to complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:637 -#, python-format -msgid "Error creating snapshot of server: %s" -msgstr "" - -#: openstackclient/compute/v2/server.py:639 -msgid "" -"\n" -"Error creating server snapshot" -msgstr "" - -#: openstackclient/compute/v2/server.py:656 -msgid "Server(s) to delete (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:661 -msgid "Wait for delete to complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:679 -#, python-format -msgid "Error deleting server: %s" -msgstr "" - -#: openstackclient/compute/v2/server.py:681 -msgid "" -"\n" -"Error deleting server" -msgstr "" - -#: openstackclient/compute/v2/server.py:693 -msgid "Only return instances that match the reservation" -msgstr "" - -#: openstackclient/compute/v2/server.py:698 -msgid "Regular expression to match IP addresses" -msgstr "" - -#: openstackclient/compute/v2/server.py:703 -msgid "Regular expression to match IPv6 addresses" -msgstr "" - -#: openstackclient/compute/v2/server.py:708 -msgid "Regular expression to match names" -msgstr "" - -#: openstackclient/compute/v2/server.py:713 -msgid "Regular expression to match instance name (admin only)" -msgstr "" - -#: openstackclient/compute/v2/server.py:719 -msgid "Search by server status" -msgstr "" - -#: openstackclient/compute/v2/server.py:724 -msgid "Search by flavor (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:729 -msgid "Search by image (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:734 -msgid "Search by hostname" -msgstr "" - -#: openstackclient/compute/v2/server.py:740 -msgid "Include all projects (admin only)" -msgstr "" - -#: openstackclient/compute/v2/server.py:750 -msgid "Search by user (admin only) (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:905 -msgid "Server(s) to lock (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:942 -msgid "Target hostname" -msgstr "" - -#: openstackclient/compute/v2/server.py:950 -msgid "Perform a shared live migration (default)" -msgstr "" - -#: openstackclient/compute/v2/server.py:956 -msgid "Perform a block live migration" -msgstr "" - -#: openstackclient/compute/v2/server.py:963 -msgid "Allow disk over-commit on the destination host" -msgstr "" - -#: openstackclient/compute/v2/server.py:970 -msgid "Do not over-commit disk on the destination host (default)" -msgstr "" - -#: openstackclient/compute/v2/server.py:976 -#: openstackclient/compute/v2/server.py:1256 -msgid "Wait for resize to complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:1003 -#: openstackclient/compute/v2/server.py:1280 -msgid "Complete\n" -msgstr "" - -#: openstackclient/compute/v2/server.py:1005 -msgid "" -"\n" -"Error migrating server" -msgstr "" - -#: openstackclient/compute/v2/server.py:1018 -msgid "Server(s) to pause (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1048 -msgid "Perform a hard reboot" -msgstr "" - -#: openstackclient/compute/v2/server.py:1056 -msgid "Perform a soft reboot" -msgstr "" - -#: openstackclient/compute/v2/server.py:1061 -msgid "Wait for reboot to complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:1077 -msgid "" -"\n" -"Reboot complete\n" -msgstr "" - -#: openstackclient/compute/v2/server.py:1079 -msgid "" -"\n" -"Error rebooting server\n" -msgstr "" - -#: openstackclient/compute/v2/server.py:1096 -msgid "" -"Recreate server from the specified image (name or ID). Defaults to the " -"currently used one." -msgstr "" - -#: openstackclient/compute/v2/server.py:1107 -msgid "Wait for rebuild to complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:1128 -msgid "" -"\n" -"Complete\n" -msgstr "" - -#: openstackclient/compute/v2/server.py:1130 -msgid "" -"\n" -"Error rebuilding server" -msgstr "" - -#: openstackclient/compute/v2/server.py:1145 -msgid "Name or ID of server to use" -msgstr "" - -#: openstackclient/compute/v2/server.py:1150 -msgid "Name or ID of security group to remove from server" -msgstr "" - -#: openstackclient/compute/v2/server.py:1182 -msgid "Volume to remove (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1241 -msgid "Resize server to specified flavor" -msgstr "" - -#: openstackclient/compute/v2/server.py:1246 -msgid "Confirm server resize is complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:1251 -msgid "Restore server state before resize" -msgstr "" - -#: openstackclient/compute/v2/server.py:1282 -msgid "" -"\n" -"Error resizing server" -msgstr "" - -#: openstackclient/compute/v2/server.py:1299 -msgid "Server(s) to restore (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1321 -msgid "Server(s) to resume (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1353 -msgid "Set new root password (interactive only)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1359 -msgid "" -"Property to add/change for this server (repeat option to set multiple " -"properties)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1382 -msgid "New password: " -msgstr "" - -#: openstackclient/compute/v2/server.py:1383 -msgid "Retype new password: " -msgstr "" - -#: openstackclient/compute/v2/server.py:1387 -msgid "Passwords do not match, password unchanged" -msgstr "" - -#: openstackclient/compute/v2/server.py:1400 -msgid "Server(s) to shelve (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1427 -msgid "Display server diagnostics information" -msgstr "" - -#: openstackclient/compute/v2/server.py:1439 -msgid "Error retrieving diagnostics data" -msgstr "" - -#: openstackclient/compute/v2/server.py:1460 -msgid "Login name (ssh -l option)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1472 -msgid "Destination port (ssh -p option)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1484 -msgid "Private key file (ssh -i option)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1495 -msgid "Options in ssh_config(5) format (ssh -o option)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1509 -msgid "Use only IPv4 addresses" -msgstr "" - -#: openstackclient/compute/v2/server.py:1516 -msgid "Use only IPv6 addresses" -msgstr "" - -#: openstackclient/compute/v2/server.py:1525 -msgid "Use public IP address" -msgstr "" - -#: openstackclient/compute/v2/server.py:1533 -msgid "Use private IP address" -msgstr "" - -#: openstackclient/compute/v2/server.py:1540 -msgid "Use other IP address (public, private, etc)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1600 -msgid "Server(s) to start (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1622 -msgid "Server(s) to stop (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1644 -msgid "Server(s) to suspend (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1667 -msgid "Server(s) to unlock (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1690 -msgid "Server(s) to unpause (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1740 -msgid "" -"Property key to remove from server (repeat option to remove multiple " -"values)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1768 -msgid "Server(s) to unshelve (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/service.py:138 -msgid "argument --disable-reason has been ignored" -msgstr "" - -#: openstackclient/identity/v2_0/catalog.py:67 -#: openstackclient/identity/v3/catalog.py:64 -msgid "Service to display (type or name)" -msgstr "" - -#: openstackclient/identity/v2_0/ec2creds.py:34 -#: openstackclient/identity/v3/ec2creds.py:59 -msgid "" -"Create credentials in project (name or ID; default: current authenticated" -" project)" -msgstr "" - -#: openstackclient/identity/v2_0/ec2creds.py:42 -#: openstackclient/identity/v3/ec2creds.py:67 -msgid "" -"Create credentials for user (name or ID; default: current authenticated " -"user)" -msgstr "" - -#: openstackclient/identity/v2_0/ec2creds.py:90 -#: openstackclient/identity/v2_0/ec2creds.py:157 -#: openstackclient/identity/v3/ec2creds.py:120 -#: openstackclient/identity/v3/ec2creds.py:172 -msgid "Credentials access key" -msgstr "" - -#: openstackclient/identity/v2_0/ec2creds.py:95 -#: openstackclient/identity/v3/ec2creds.py:125 -msgid "Delete credentials for user (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/ec2creds.py:122 -#: openstackclient/identity/v3/ec2creds.py:144 -msgid "Filter list by user (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/ec2creds.py:162 -#: openstackclient/identity/v3/ec2creds.py:177 -msgid "Show credentials for user (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:34 -msgid "New endpoint service (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:40 -msgid "New endpoint public URL (required)" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:45 -msgid "New endpoint admin URL" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:50 -msgid "New endpoint internal URL" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:55 -msgid "New endpoint region ID" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:84 -msgid "Endpoint ID to delete" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:133 -msgid "Endpoint ID to display" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:36 -msgid "New project name" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:41 -msgid "Project description" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:47 -msgid "Enable project (default)" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:52 -#: openstackclient/identity/v2_0/project.py:179 -msgid "Disable project" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:58 -msgid "Add a property to <name> (repeat option to set multiple properties)" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:64 -#: openstackclient/identity/v3/project.py:75 -msgid "Return existing project" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:109 -msgid "Project(s) to delete (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:158 -#: openstackclient/identity/v2_0/project.py:291 -msgid "Project to modify (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:163 -msgid "Set project name" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:168 -msgid "Set project description" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:174 -msgid "Enable project" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:185 -msgid "Set a project property (repeat option to set multiple properties)" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:234 -msgid "Project to display (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:298 -msgid "Unset a project property (repeat option to unset multiple properties)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:36 -msgid "Role to add to <project>:<user> (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:42 -#: openstackclient/identity/v2_0/role.py:288 -msgid "Include <project> (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:48 -#: openstackclient/identity/v2_0/role.py:294 -msgid "Include <user> (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:79 -msgid "New role name" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:84 -#: openstackclient/identity/v3/role.py:155 -msgid "Return existing role" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:116 -msgid "Role(s) to delete (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:178 -#: openstackclient/identity/v2_0/role.py:238 -msgid "Project must be specified" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:192 -#: openstackclient/identity/v2_0/role.py:244 -msgid "User must be specified" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:218 -msgid "User to list (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:223 -msgid "Filter users by <project> (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:282 -msgid "Role to remove (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:320 -msgid "Role to display (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:36 -msgid "New service type (compute, image, identity, volume, etc)" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:47 -msgid "New service name" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:52 -msgid "New service description" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:71 -msgid "" -"The argument --type is deprecated, use service create --name <service-" -"name> type instead." -msgstr "" - -#: openstackclient/identity/v2_0/service.py:96 -msgid "Service to delete (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:140 -msgid "Service to display (type, name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:146 -msgid "Show service catalog information" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:163 -#, python-format -msgid "No service catalog with a type, name or ID of '%s' exists." -msgstr "" - -#: openstackclient/identity/v2_0/token.py:50 -msgid "Token to be deleted" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:35 -msgid "New user name" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:40 -msgid "Default project (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:45 -#: openstackclient/identity/v2_0/user.py:258 -msgid "Set user password" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:51 -#: openstackclient/identity/v2_0/user.py:264 -msgid "Prompt interactively for password" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:56 -#: openstackclient/identity/v2_0/user.py:269 -msgid "Set user email address" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:62 -#: openstackclient/identity/v2_0/user.py:275 -msgid "Enable user (default)" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:67 -#: openstackclient/identity/v2_0/user.py:280 -msgid "Disable user" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:72 -#: openstackclient/identity/v3/user.py:86 -msgid "Return existing user" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:133 -msgid "User(s) to delete (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:156 -msgid "Filter users by project (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:243 -msgid "User to change (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:248 -msgid "Set user name" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:253 -msgid "Set default project (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:342 -msgid "User to display (name or ID)" -msgstr "" - -#: openstackclient/identity/v3/domain.py:57 -msgid "Return existing domain" -msgstr "" - -#: openstackclient/identity/v3/group.py:130 -msgid "Return existing group" -msgstr "" - -#: openstackclient/identity/v3/region.py:33 -msgid "New region ID" -msgstr "" - -#: openstackclient/identity/v3/region.py:38 -msgid "Parent region ID" -msgstr "" - -#: openstackclient/identity/v3/region.py:43 -#: openstackclient/identity/v3/region.py:128 -msgid "New region description" -msgstr "" - -#: openstackclient/identity/v3/region.py:70 -msgid "Region ID to delete" -msgstr "" - -#: openstackclient/identity/v3/region.py:88 -msgid "Filter by parent region ID" -msgstr "" - -#: openstackclient/identity/v3/region.py:118 -msgid "Region to modify" -msgstr "" - -#: openstackclient/identity/v3/region.py:123 -msgid "New parent region ID" -msgstr "" - -#: openstackclient/identity/v3/region.py:154 -msgid "Region to display" -msgstr "" - -#: openstackclient/image/v1/image.py:191 openstackclient/image/v1/image.py:609 -#: openstackclient/image/v2/image.py:283 openstackclient/image/v2/image.py:794 -msgid "The --owner option is deprecated, please use --project instead." -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:67 -msgid "Network to allocate floating IP from (name or ID)" -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:75 -msgid "Subnet on which you want to create the floating IP (name or ID)" -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:81 -msgid "Port to be associated with the floating IP (name or ID)" -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:88 -msgid "Floating IP address" -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:94 -msgid "Fixed IP address mapped to the floating IP" -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:119 -msgid "Floating IP to delete (IP address or ID)" -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:193 -msgid "Floating IP to display (IP address or ID)" -msgstr "" - -#: openstackclient/network/v2/network.py:114 -msgid "New network name" -msgstr "" - -#: openstackclient/network/v2/network.py:121 -#: openstackclient/network/v2/network.py:385 -msgid "Share the network between projects" -msgstr "" - -#: openstackclient/network/v2/network.py:126 -#: openstackclient/network/v2/network.py:390 -msgid "Do not share the network between projects" -msgstr "" - -#: openstackclient/network/v2/network.py:136 -msgid "Enable network (default)" -msgstr "" - -#: openstackclient/network/v2/network.py:141 -#: openstackclient/network/v2/network.py:378 -msgid "Disable network" -msgstr "" - -#: openstackclient/network/v2/network.py:146 -#: openstackclient/network/v2/port.py:246 -#: openstackclient/network/v2/router.py:174 -#: openstackclient/network/v2/security_group.py:116 -#: openstackclient/network/v2/security_group_rule.py:127 -#: openstackclient/network/v2/subnet.py:183 -#: openstackclient/network/v2/subnet_pool.py:114 -msgid "Owner's project (name or ID)" -msgstr "" - -#: openstackclient/network/v2/network.py:154 -msgid "" -"Availability Zone in which to create this network (Network Availability " -"Zone extension required, repeat option to set multiple availability " -"zones)" -msgstr "" - -#: openstackclient/network/v2/network.py:162 -#: openstackclient/network/v2/network.py:396 -msgid "Set this network as an external network (external-net extension required)" -msgstr "" - -#: openstackclient/network/v2/network.py:168 -msgid "Set this network as an internal network (default)" -msgstr "" - -#: openstackclient/network/v2/network.py:174 -msgid "Specify if this network should be used as the default external network" -msgstr "" - -#: openstackclient/network/v2/network.py:180 -msgid "Do not use the network as the default external network. (default)" -msgstr "" - -#: openstackclient/network/v2/network.py:188 -msgid "" -"The physical mechanism by which the virtual network is implemented. The " -"supported options are: flat, gre, local, vlan, vxlan" -msgstr "" - -#: openstackclient/network/v2/network.py:196 -msgid "Name of the physical network over which the virtual network is implemented" -msgstr "" - -#: openstackclient/network/v2/network.py:203 -msgid "VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN networks" -msgstr "" - -#: openstackclient/network/v2/network.py:212 -msgid "IPv4 subnet for fixed IPs (in CIDR notation)" -msgstr "" - -#: openstackclient/network/v2/network.py:361 -msgid "Network to modify (name or ID)" -msgstr "" - -#: openstackclient/network/v2/network.py:366 -msgid "Set network name" -msgstr "" - -#: openstackclient/network/v2/network.py:373 -msgid "Enable network" -msgstr "" - -#: openstackclient/network/v2/network.py:402 -msgid "Set this network as an internal network" -msgstr "" - -#: openstackclient/network/v2/network.py:408 -msgid "Set the network as the default external network" -msgstr "" - -#: openstackclient/network/v2/network.py:413 -msgid "Do not use the network as the default external network" -msgstr "" - -#: openstackclient/network/v2/network.py:436 -msgid "Network to display (name or ID)" -msgstr "" - -#: openstackclient/network/v2/port.py:73 -msgid "The --device-id option is deprecated, please use --device instead." -msgstr "" - -#: openstackclient/network/v2/port.py:79 -msgid "The --host-id option is deprecated, please use --host instead." -msgstr "" - -#: openstackclient/network/v2/port.py:161 -msgid "Port device ID" -msgstr "" - -#: openstackclient/network/v2/port.py:171 -msgid "Device owner of this port" -msgstr "" - -#: openstackclient/network/v2/port.py:178 -msgid "" -"VNIC type for this port (direct | direct-physical | macvtap | normal | " -"baremetal, default: normal)" -msgstr "" - -#: openstackclient/network/v2/port.py:187 -msgid "Allocate port on host <host-id> (ID only)" -msgstr "" - -#: openstackclient/network/v2/port.py:206 -msgid "Network this port belongs to (name or ID)" -msgstr "" - -#: openstackclient/network/v2/port.py:214 -#: openstackclient/network/v2/port.py:365 -msgid "" -"Desired IP and/or subnet (name or ID) for this port: subnet=<subnet>,ip-" -"address=<ip-address> (repeat option to set multiple fixed IP addresses)" -msgstr "" - -#: openstackclient/network/v2/port.py:222 -#: openstackclient/network/v2/port.py:379 -msgid "" -"Custom data to be passed as binding:profile: <key>=<value> (repeat option" -" to set multiple binding:profile data)" -msgstr "" - -#: openstackclient/network/v2/port.py:231 -msgid "Enable port (default)" -msgstr "" - -#: openstackclient/network/v2/port.py:236 -#: openstackclient/network/v2/port.py:352 -msgid "Disable port" -msgstr "" - -#: openstackclient/network/v2/port.py:241 -msgid "MAC address of this port" -msgstr "" - -#: openstackclient/network/v2/port.py:280 -msgid "Port(s) to delete (name or ID)" -msgstr "" - -#: openstackclient/network/v2/port.py:301 -msgid "List only ports attached to this router (name or ID)" -msgstr "" - -#: openstackclient/network/v2/port.py:347 -msgid "Enable port" -msgstr "" - -#: openstackclient/network/v2/port.py:357 -msgid "Set port name" -msgstr "" - -#: openstackclient/network/v2/port.py:372 -msgid "Clear existing information of fixed IP addresses" -msgstr "" - -#: openstackclient/network/v2/port.py:386 -msgid "Clear existing information of binding:profile" -msgstr "" - -#: openstackclient/network/v2/port.py:391 -msgid "Port to modify (name or ID)" -msgstr "" - -#: openstackclient/network/v2/port.py:429 -msgid "Port to display (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:99 -msgid "Router to which port will be added (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:104 -msgid "Port to be added (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:123 -msgid "Router to which subnet will be added (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:128 -msgid "Subnet to be added (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:150 -msgid "New router name" -msgstr "" - -#: openstackclient/network/v2/router.py:157 -msgid "Enable router (default)" -msgstr "" - -#: openstackclient/network/v2/router.py:162 -#: openstackclient/network/v2/router.py:351 -msgid "Disable router" -msgstr "" - -#: openstackclient/network/v2/router.py:169 -msgid "Create a distributed router" -msgstr "" - -#: openstackclient/network/v2/router.py:182 -msgid "" -"Availability Zone in which to create this router (Router Availability " -"Zone extension required, repeat option to set multiple availability " -"zones)" -msgstr "" - -#: openstackclient/network/v2/router.py:210 -msgid "Router(s) to delete (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:283 -msgid "Router from which port will be removed (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:288 -msgid "Port to be removed (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:307 -msgid "Router from which the subnet will be removed (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:312 -msgid "Subnet to be removed (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:334 -msgid "Router to modify (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:339 -msgid "Set router name" -msgstr "" - -#: openstackclient/network/v2/router.py:346 -msgid "Enable router" -msgstr "" - -#: openstackclient/network/v2/router.py:357 -msgid "Set router to distributed mode (disabled router only)" -msgstr "" - -#: openstackclient/network/v2/router.py:362 -msgid "Set router to centralized mode (disabled router only)" -msgstr "" - -#: openstackclient/network/v2/router.py:372 -msgid "" -"Routes associated with the router destination: destination subnet (in " -"CIDR notation) gateway: nexthop IP address (repeat option to set multiple" -" routes)" -msgstr "" - -#: openstackclient/network/v2/router.py:380 -msgid "Clear routes associated with the router" -msgstr "" - -#: openstackclient/network/v2/router.py:412 -msgid "Router to display (name or ID)" -msgstr "" - -#: openstackclient/network/v2/security_group.py:103 -#: openstackclient/network/v2/security_group.py:249 -msgid "New security group name" -msgstr "" - -#: openstackclient/network/v2/security_group.py:108 -msgid "Security group description" -msgstr "" - -#: openstackclient/network/v2/security_group.py:173 -msgid "Security group to delete (name or ID)" -msgstr "" - -#: openstackclient/network/v2/security_group.py:208 -msgid "Display information from all projects (admin only)" -msgstr "" - -#: openstackclient/network/v2/security_group.py:244 -msgid "Security group to modify (name or ID)" -msgstr "" - -#: openstackclient/network/v2/security_group.py:254 -msgid "New security group description" -msgstr "" - -#: openstackclient/network/v2/security_group.py:299 -msgid "Security group to display (name or ID)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:82 -msgid "IP protocol (icmp, tcp, udp; default: tcp)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:88 -msgid "" -"Source IP address block (may use CIDR notation; default for IPv4 rule: " -"0.0.0.0/0)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:94 -msgid "Source security group (name or ID)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:101 -msgid "" -"Destination port, may be a single port or port range: 137:139 (only " -"required for IP protocols tcp and udp)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:111 -msgid "Rule applies to incoming network traffic (default)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:116 -msgid "Rule applies to outgoing network traffic" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:122 -msgid "Ethertype of network traffic (IPv4, IPv6; default: IPv4)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:221 -msgid "Security group rule to delete (ID only)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:241 -msgid "List all rules in this security group (name or ID)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:336 -msgid "Security group rule to display (ID only)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:54 -msgid "" -"Allocation pool IP addresses for this subnet e.g.: " -"start=192.168.199.2,end=192.168.199.254 (repeat option to add multiple IP" -" addresses)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:63 -msgid "DNS server for this subnet (repeat option to set multiple DNS servers)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:72 -msgid "" -"Additional route for 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 " -"add multiple routes)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:178 -msgid "New subnet name" -msgstr "" - -#: openstackclient/network/v2/subnet.py:190 -msgid "Subnet pool from which this subnet will obtain a CIDR (Name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:196 -msgid "Use default subnet pool for --ip-version" -msgstr "" - -#: openstackclient/network/v2/subnet.py:201 -msgid "Prefix length for subnet allocation from subnet pool" -msgstr "" - -#: openstackclient/network/v2/subnet.py:206 -msgid "" -"Subnet range in CIDR notation (required if --subnet-pool is not " -"specified, optional otherwise)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:215 -msgid "Enable DHCP (default)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:220 -#: openstackclient/network/v2/subnet.py:348 -msgid "Disable DHCP" -msgstr "" - -#: openstackclient/network/v2/subnet.py:226 -msgid "" -"Specify a gateway for the subnet. The three options are: <ip-address>: " -"Specific IP address to use as the gateway, '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')" -msgstr "" - -#: openstackclient/network/v2/subnet.py:238 -msgid "" -"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" -msgstr "" - -#: openstackclient/network/v2/subnet.py:245 -msgid "" -"IPv6 RA (Router Advertisement) mode, valid modes: [dhcpv6-stateful, " -"dhcpv6-stateless, slaac]" -msgstr "" - -#: openstackclient/network/v2/subnet.py:251 -msgid "IPv6 address mode, valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]" -msgstr "" - -#: openstackclient/network/v2/subnet.py:258 -msgid "Network this subnet belongs to (name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:280 -msgid "Subnet to delete (name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:331 -msgid "Subnet to modify (name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:336 -msgid "Updated name of the subnet" -msgstr "" - -#: openstackclient/network/v2/subnet.py:343 -msgid "Enable DHCP" -msgstr "" - -#: openstackclient/network/v2/subnet.py:353 -msgid "" -"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" -msgstr "" - -#: openstackclient/network/v2/subnet.py:387 -msgid "Subnet to display (name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:77 -msgid "" -"Set subnet pool prefixes (in CIDR notation) (repeat option to set " -"multiple prefixes)" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:84 -msgid "Set subnet pool default prefix length" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:90 -msgid "Set subnet pool minimum prefix length" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:96 -msgid "Set subnet pool maximum prefix length" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:108 -msgid "Name of the new subnet pool" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:120 -#: openstackclient/network/v2/subnet_pool.py:226 -msgid "" -"Set address scope associated with the subnet pool (name or ID), prefixes " -"must be unique across address scopes" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:146 -msgid "Subnet pool to delete (name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:214 -msgid "Subnet pool to modify (name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:219 -msgid "Set subnet pool name" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:233 -msgid "Remove address scope associated with the subnet pool" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:262 -msgid "Subnet pool to display (name or ID)" -msgstr "" - diff --git a/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po b/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po index a121aca..d9bbf6f 100644 --- a/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po +++ b/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po @@ -7,9 +7,9 @@ # Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 2.2.1.dev235\n" +"Project-Id-Version: python-openstackclient 2.5.1.dev51\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-19 05:02+0000\n" +"POT-Creation-Date: 2016-06-03 19:37+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -21,62 +21,6 @@ msgstr "" "X-Generator: Zanata 3.7.3\n" "Language-Team: Chinese (Taiwan)\n" -msgid "" -"\n" -"Complete\n" -msgstr "" -"\n" -"完成\n" - -msgid "" -"\n" -"Error creating server" -msgstr "" -"\n" -"新增雲實例時出錯" - -msgid "" -"\n" -"Error creating server snapshot" -msgstr "" -"\n" -"新增雲實例即時存檔時出錯" - -msgid "" -"\n" -"Error migrating server" -msgstr "" -"\n" -"轉移雲實例出錯" - -msgid "" -"\n" -"Error rebooting server\n" -msgstr "" -"\n" -"重開雲實例出錯\n" - -msgid "" -"\n" -"Error rebuilding server" -msgstr "" -"\n" -"重建雲實例出錯" - -msgid "" -"\n" -"Error resizing server" -msgstr "" -"\n" -"調整雲實例容量時出錯" - -msgid "" -"\n" -"Reboot complete\n" -msgstr "" -"\n" -"重開機完成\n" - msgid "Add a property to <name> (repeat option to set multiple properties)" msgstr "加入屬性到 <name>(重復這選項來設定多個屬性)" @@ -122,9 +66,6 @@ msgstr "啟用用戶(預設)" msgid "Endpoint ID to delete" msgstr "要刪除的端點識別號" -msgid "Endpoint ID to display" -msgstr "要顯示的端點識別號" - #, python-format msgid "Error creating server: %s" msgstr "新增雲實例時出錯:%s" diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index be06d2b..d711f4f 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -17,6 +17,7 @@ from openstack import connection from openstack import profile from openstackclient.common import utils +from openstackclient.i18n import _ LOG = logging.getLogger(__name__) @@ -51,7 +52,7 @@ def build_option_parser(parser): '--os-network-api-version', metavar='<network-api-version>', default=utils.env('OS_NETWORK_API_VERSION'), - help='Network API version, default=' + - DEFAULT_API_VERSION + - ' (Env: OS_NETWORK_API_VERSION)') + help=_("Network API version, default=%s " + "(Env: OS_NETWORK_API_VERSION)") % DEFAULT_API_VERSION + ) return parser diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index a3047d8..4028ac0 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -16,6 +16,7 @@ import six from openstackclient.common import command from openstackclient.common import exceptions +from openstackclient.i18n import _ @six.add_metaclass(abc.ABCMeta) @@ -94,14 +95,22 @@ class NetworkAndComputeDelete(NetworkAndComputeCommand): self.take_action_compute(self.app.client_manager.compute, parsed_args) except Exception as e: - self.app.log.error("Failed to delete %s with name or ID " - "'%s': %s" % (self.resource, r, e)) + msg = _("Failed to delete %(resource)s with name or ID " + "'%(name_or_id)s': %(e)s") % { + "resource": self.resource, + "name_or_id": r, + "e": e, + } + self.app.log.error(msg) ret += 1 if ret: total = len(resources) - msg = "%s of %s %ss failed to delete." % (ret, total, - self.resource) + msg = _("%(num)s of %(total)s %(resource)s failed to delete.") % { + "num": ret, + "total": total, + "resource": self.resource, + } raise exceptions.CommandError(msg) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 614900c..dbc6865 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -98,22 +98,38 @@ class CreateAddressScope(command.ShowOne): class DeleteAddressScope(command.Command): - """Delete an address scope""" + """Delete address scope(s)""" def get_parser(self, prog_name): parser = super(DeleteAddressScope, self).get_parser(prog_name) parser.add_argument( 'address_scope', metavar="<address-scope>", - help=_("Address scope to delete (name or ID)") + nargs='+', + help=_("Address scope(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network - obj = client.find_address_scope(parsed_args.address_scope) - client.delete_address_scope(obj) + result = 0 + + for scope in parsed_args.address_scope: + try: + obj = client.find_address_scope(scope, ignore_missing=False) + 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}) + + if result > 0: + total = len(parsed_args.address_scope) + msg = (_("%(result)s of %(total)s address scopes failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListAddressScope(command.Lister): diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py new file mode 100644 index 0000000..cc24033 --- /dev/null +++ b/openstackclient/network/v2/ip_availability.py @@ -0,0 +1,109 @@ +# 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. +# + +"""IP Availability Info implementations""" + +from openstackclient.common import command +from openstackclient.common import utils +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + + +_formatters = { + 'subnet_ip_availability': utils.format_list_of_dicts, +} + + +def _get_columns(item): + columns = list(item.keys()) + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + return tuple(sorted(columns)) + + +class ListIPAvailability(command.Lister): + """List IP availability for network""" + + def get_parser(self, prog_name): + parser = super(ListIPAvailability, self).get_parser(prog_name) + parser.add_argument( + '--ip-version', + type=int, + choices=[4, 6], + metavar='<ip-version>', + dest='ip_version', + help=_("List IP availability of given IP version networks"), + ) + parser.add_argument( + '--project', + metavar='<project>', + help=_("List IP availability of given project"), + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'network_id', + 'network_name', + 'total_ips', + 'used_ips', + ) + column_headers = ( + 'Network ID', + 'Network Name', + 'Total IPs', + 'Used IPs', + ) + + filters = {} + if parsed_args.ip_version: + filters['ip_version'] = parsed_args.ip_version + + if parsed_args.project: + identity_client = self.app.client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + filters['tenant_id'] = project_id + data = client.network_ip_availabilities(**filters) + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ShowIPAvailability(command.ShowOne): + """Show network IP availability details""" + + def get_parser(self, prog_name): + parser = super(ShowIPAvailability, self).get_parser(prog_name) + parser.add_argument( + 'network', + metavar="<network>", + help=_("Show IP availability for a specific network (name or ID)"), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_network_ip_availability(parsed_args.network, + ignore_missing=False) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + return columns, data diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py new file mode 100644 index 0000000..818ffc0 --- /dev/null +++ b/openstackclient/network/v2/network_segment.py @@ -0,0 +1,115 @@ +# 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 segment action implementations""" + +# TODO(rtheis): Add description and name properties when support is available. + +from openstackclient.common import command +from openstackclient.common import utils +from openstackclient.i18n import _ + + +class ListNetworkSegment(command.Lister): + """List network segments + + (Caution: This is a beta command and subject to change. + Use global option --os-beta-command to enable + this command) + """ + + def get_parser(self, prog_name): + parser = super(ListNetworkSegment, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_('List additional fields in output'), + ) + parser.add_argument( + '--network', + metavar='<network>', + help=_('List network segments that belong to this ' + 'network (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + self.validate_os_beta_command_enabled() + + network_client = self.app.client_manager.network + + filters = {} + if parsed_args.network: + _network = network_client.find_network( + parsed_args.network, + ignore_missing=False + ) + filters = {'network_id': _network.id} + data = network_client.segments(**filters) + + headers = ( + 'ID', + 'Network', + 'Network Type', + 'Segment', + ) + columns = ( + 'id', + 'network_id', + 'network_type', + 'segmentation_id', + ) + if parsed_args.long: + headers = headers + ( + 'Physical Network', + ) + columns = columns + ( + 'physical_network', + ) + + return (headers, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class ShowNetworkSegment(command.ShowOne): + """Display network segment details + + (Caution: This is a beta command and subject to change. + Use global option --os-beta-command to enable + this command) + """ + + def get_parser(self, prog_name): + parser = super(ShowNetworkSegment, self).get_parser(prog_name) + parser.add_argument( + 'network_segment', + metavar='<network-segment>', + help=_('Network segment to display (ID only)'), + ) + return parser + + def take_action(self, parsed_args): + self.validate_os_beta_command_enabled() + + client = self.app.client_manager.network + obj = client.find_segment( + parsed_args.network_segment, + ignore_missing=False + ) + columns = tuple(sorted(obj.keys())) + data = utils.get_item_properties(obj, columns) + return (columns, data) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index ca02281..7ef3964 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -284,10 +284,23 @@ class DeletePort(command.Command): def take_action(self, parsed_args): client = self.app.client_manager.network + result = 0 for port in parsed_args.port: - res = client.find_port(port) - client.delete_port(res) + try: + obj = client.find_port(port, ignore_missing=False) + 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}) + + if result > 0: + total = len(parsed_args.port) + msg = (_("%(result)s of %(total)s ports failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListPort(command.Lister): diff --git a/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml b/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml new file mode 100644 index 0000000..c783d01 --- /dev/null +++ b/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml @@ -0,0 +1,6 @@ +--- +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/openstackclient/shell.py b/openstackclient/shell.py index 9968d73..9179ad0 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -251,7 +251,7 @@ class OpenStackShell(app.App): help="Print API call timing info", ) parser.add_argument( - '--enable-beta-commands', + '--os-beta-command', action='store_true', help="Enable beta commands which are subject to change", ) diff --git a/openstackclient/tests/common/test_command.py b/openstackclient/tests/common/test_command.py index 7467d9e..722a4c0 100644 --- a/openstackclient/tests/common/test_command.py +++ b/openstackclient/tests/common/test_command.py @@ -15,6 +15,8 @@ import mock 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 @@ -31,3 +33,16 @@ class TestCommand(test_utils.TestCase): self.assertTrue(hasattr(cmd, 'log')) self.assertEqual('openstackclient.tests.common.test_command.' 'FakeCommand', cmd.log.name) + + def test_validate_os_beta_command_enabled(self): + cmd = FakeCommand(mock.Mock(), mock.Mock()) + cmd.app = mock.Mock() + cmd.app.options = test_fakes.FakeOptions() + + # No exception is raised when enabled. + cmd.app.options.os_beta_command = True + cmd.validate_os_beta_command_enabled() + + cmd.app.options.os_beta_command = False + self.assertRaises(exceptions.CommandError, + cmd.validate_os_beta_command_enabled) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 62a46b1..c9e2025 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -111,6 +111,9 @@ class FakeAggregate(object): class FakeComputev2Client(object): def __init__(self, **kwargs): + self.agents = mock.Mock() + self.agents.resource_class = fakes.FakeResource(None, {}) + self.aggregates = mock.Mock() self.aggregates.resource_class = fakes.FakeResource(None, {}) @@ -132,6 +135,9 @@ class FakeComputev2Client(object): self.flavors = mock.Mock() self.flavors.resource_class = fakes.FakeResource(None, {}) + self.flavor_access = mock.Mock() + self.flavor_access.resource_class = fakes.FakeResource(None, {}) + self.quotas = mock.Mock() self.quotas.resource_class = fakes.FakeResource(None, {}) @@ -204,6 +210,55 @@ class TestComputev2(utils.TestCommand): ) +class FakeAgent(object): + """Fake one or more agent.""" + + @staticmethod + def create_one_agent(attrs=None): + """Create a fake agent. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with agent_id, os, and so on + """ + + attrs = attrs or {} + + # set default attributes. + agent_info = { + 'agent_id': 'agent-id-' + uuid.uuid4().hex, + 'os': 'agent-os-' + uuid.uuid4().hex, + 'architecture': 'agent-architecture', + 'version': '8.0', + 'url': 'http://127.0.0.1', + 'md5hash': 'agent-md5hash', + 'hypervisor': 'hypervisor', + } + agent_info.update(attrs) + + agent = fakes.FakeResource(info=copy.deepcopy(agent_info), + loaded=True) + return agent + + @staticmethod + def create_agents(attrs=None, count=2): + """Create multiple fake agents. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of agents to fake + :return: + A list of FakeResource objects faking the agents + """ + agents = [] + for i in range(0, count): + agents.append(FakeAgent.create_one_agent(attrs)) + + return agents + + class FakeHypervisor(object): """Fake one or more hypervisor.""" diff --git a/openstackclient/tests/compute/v2/test_agent.py b/openstackclient/tests/compute/v2/test_agent.py new file mode 100644 index 0000000..d3d1ff2 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_agent.py @@ -0,0 +1,254 @@ +# Copyright 2016 Easystack. 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 mock import call +from openstackclient.common import exceptions +from openstackclient.compute.v2 import agent +from openstackclient.tests.compute.v2 import fakes as compute_fakes + + +class TestAgent(compute_fakes.TestComputev2): + + fake_agent = compute_fakes.FakeAgent.create_one_agent() + + columns = ( + 'agent_id', + 'architecture', + 'hypervisor', + 'md5hash', + 'os', + 'url', + 'version', + ) + + data = ( + fake_agent.agent_id, + fake_agent.architecture, + fake_agent.hypervisor, + fake_agent.md5hash, + fake_agent.os, + fake_agent.url, + fake_agent.version, + ) + + def setUp(self): + super(TestAgent, self).setUp() + + self.agents_mock = self.app.client_manager.compute.agents + self.agents_mock.reset_mock() + + +class TestAgentCreate(TestAgent): + + def setUp(self): + super(TestAgentCreate, self).setUp() + + self.agents_mock.create.return_value = self.fake_agent + self.cmd = agent.CreateAgent(self.app, None) + + def test_agent_create(self): + arglist = [ + self.fake_agent.os, + self.fake_agent.architecture, + self.fake_agent.version, + self.fake_agent.url, + self.fake_agent.md5hash, + self.fake_agent.hypervisor, + ] + + verifylist = [ + ('os', self.fake_agent.os), + ('architecture', self.fake_agent.architecture), + ('version', self.fake_agent.version), + ('url', self.fake_agent.url), + ('md5hash', self.fake_agent.md5hash), + ('hypervisor', self.fake_agent.hypervisor), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.agents_mock.create.assert_called_with(parsed_args.os, + parsed_args.architecture, + parsed_args.version, + parsed_args.url, + parsed_args.md5hash, + parsed_args.hypervisor) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestAgentDelete(TestAgent): + + fake_agents = compute_fakes.FakeAgent.create_agents(count=2) + + def setUp(self): + super(TestAgentDelete, self).setUp() + + self.agents_mock.get.return_value = self.fake_agents + self.cmd = agent.DeleteAgent(self.app, None) + + def test_delete_one_agent(self): + arglist = [ + self.fake_agents[0].agent_id + ] + + verifylist = [ + ('id', [self.fake_agents[0].agent_id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.agents_mock.delete.assert_called_with( + self.fake_agents[0].agent_id) + self.assertIsNone(result) + + def test_delete_multiple_agents(self): + arglist = [] + for n in self.fake_agents: + arglist.append(n.agent_id) + verifylist = [ + ('id', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for n in self.fake_agents: + calls.append(call(n.agent_id)) + self.agents_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_agents_exception(self): + arglist = [ + self.fake_agents[0].agent_id, + self.fake_agents[1].agent_id, + 'x-y-z', + ] + verifylist = [ + ('id', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ret_delete = [ + None, + None, + exceptions.NotFound('404') + ] + self.agents_mock.delete = mock.Mock(side_effect=ret_delete) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + calls = [ + call(self.fake_agents[0].agent_id), + call(self.fake_agents[1].agent_id), + ] + self.agents_mock.delete.assert_has_calls(calls) + + +class TestAgentList(TestAgent): + + agents = compute_fakes.FakeAgent.create_agents(count=3) + list_columns = ( + "Agent ID", + "Hypervisor", + "OS", + "Architecture", + "Version", + "Md5Hash", + "URL", + ) + + list_data = [] + for _agent in agents: + list_data.append(( + _agent.agent_id, + _agent.hypervisor, + _agent.os, + _agent.architecture, + _agent.version, + _agent.md5hash, + _agent.url, + )) + + def setUp(self): + + super(TestAgentList, self).setUp() + + self.agents_mock.list.return_value = self.agents + self.cmd = agent.ListAgent(self.app, None) + + def test_agent_list(self): + + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.list_columns, columns) + self.assertEqual(self.list_data, list(data)) + + def test_agent_list_with_hypervisor(self): + + arglist = [ + '--hypervisor', + 'hypervisor', + ] + verifylist = [ + ('hypervisor', 'hypervisor'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.list_columns, columns) + self.assertEqual(self.list_data, list(data)) + + +class TestAgentSet(TestAgent): + + def setUp(self): + super(TestAgentSet, self).setUp() + + self.agents_mock.update.return_value = self.fake_agent + self.cmd = agent.SetAgent(self.app, None) + + def test_agent_set(self): + arglist = [ + 'id', + 'new-version', + 'new-url', + 'new-md5hash', + ] + + verifylist = [ + ('id', 'id'), + ('version', 'new-version'), + ('url', 'new-url'), + ('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, + parsed_args.version, + parsed_args.url, + parsed_args.md5hash) + self.assertIsNone(result) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 6f507b1..434d5f9 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -13,10 +13,14 @@ # under the License. # +import copy + 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 @@ -29,6 +33,13 @@ class TestFlavor(compute_fakes.TestComputev2): self.flavors_mock = self.app.client_manager.compute.flavors self.flavors_mock.reset_mock() + # Get a shortcut to the FlavorAccessManager Mock + self.flavor_access_mock = self.app.client_manager.compute.flavor_access + self.flavor_access_mock.reset_mock() + + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + class TestFlavorCreate(TestFlavor): @@ -427,16 +438,23 @@ class TestFlavorList(TestFlavor): class TestFlavorSet(TestFlavor): # Return value of self.flavors_mock.find(). - flavor = compute_fakes.FakeFlavor.create_one_flavor() + flavor = compute_fakes.FakeFlavor.create_one_flavor( + attrs={'os-flavor-access:is_public': False}) def setUp(self): super(TestFlavorSet, self).setUp() 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.cmd = flavor.SetFlavor(self.app, None) - def test_flavor_set(self): + def test_flavor_set_property(self): arglist = [ '--property', 'FOO="B A R"', 'baremetal' @@ -452,6 +470,81 @@ class TestFlavorSet(TestFlavor): is_public=None) self.assertIsNone(result) + def test_flavor_set_project(self): + arglist = [ + '--project', identity_fakes.project_id, + self.flavor.id, + ] + verifylist = [ + ('project', identity_fakes.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.flavor_access_mock.add_tenant_access.assert_called_with( + self.flavor.id, + identity_fakes.project_id, + ) + + def test_flavor_set_no_project(self): + arglist = [ + '--project', + self.flavor.id, + ] + verifylist = [ + ('project', ''), + ('flavor', self.flavor.id), + ] + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_flavor_set_no_flavor(self): + arglist = [ + '--project', identity_fakes.project_id, + ] + verifylist = [ + ('project', identity_fakes.project_id), + ] + + 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, + 'unexist_flavor', + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('flavor', 'unexist_flavor'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_flavor_set_nothing(self): + arglist = [ + self.flavor.id, + ] + verifylist = [ + ('flavor', self.flavor.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestFlavorShow(TestFlavor): @@ -521,16 +614,23 @@ class TestFlavorShow(TestFlavor): class TestFlavorUnset(TestFlavor): # Return value of self.flavors_mock.find(). - flavor = compute_fakes.FakeFlavor.create_one_flavor() + flavor = compute_fakes.FakeFlavor.create_one_flavor( + attrs={'os-flavor-access:is_public': False}) def setUp(self): super(TestFlavorUnset, self).setUp() 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.cmd = flavor.UnsetFlavor(self.app, None) - def test_flavor_unset(self): + def test_flavor_unset_property(self): arglist = [ '--property', 'property', 'baremetal' @@ -545,3 +645,80 @@ class TestFlavorUnset(TestFlavor): self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, is_public=None) self.assertIsNone(result) + + def test_flavor_unset_project(self): + arglist = [ + '--project', identity_fakes.project_id, + self.flavor.id, + ] + verifylist = [ + ('project', identity_fakes.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.flavor_access_mock.remove_tenant_access.assert_called_with( + self.flavor.id, + identity_fakes.project_id, + ) + + def test_flavor_unset_no_project(self): + arglist = [ + '--project', '', + self.flavor.id, + ] + verifylist = [ + ('project', ''), + ('flavor', self.flavor.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_flavor_unset_no_flavor(self): + arglist = [ + '--project', identity_fakes.project_id, + ] + verifylist = [ + ('project', identity_fakes.project_id), + ] + + 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, + 'unexist_flavor', + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('flavor', 'unexist_flavor'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_flavor_unset_nothing(self): + arglist = [ + self.flavor.id, + ] + verifylist = [ + ('flavor', self.flavor.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 7d184b3..d642e6d 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. # +import getpass import mock from mock import call @@ -1213,6 +1214,114 @@ class TestServerResume(TestServer): self.run_method_with_servers('resume', 3) +class TestServerSet(TestServer): + + def setUp(self): + super(TestServerSet, self).setUp() + + self.methods = { + 'update': None, + 'reset_state': None, + 'change_password': None, + } + + self.fake_servers = self.setup_servers_mock(2) + + # Get the command object to test + self.cmd = server.SetServer(self.app, None) + + def test_server_set_no_option(self): + arglist = [ + 'foo_vm' + ] + verifylist = [ + ('server', 'foo_vm') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.assertNotCalled(self.fake_servers[0].update) + self.assertNotCalled(self.fake_servers[0].reset_state) + self.assertNotCalled(self.fake_servers[0].change_password) + self.assertNotCalled(self.servers_mock.set_meta) + self.assertIsNone(result) + + def test_server_set_with_state(self): + for index, state in enumerate(['active', 'error']): + arglist = [ + '--state', state, + 'foo_vm', + ] + verifylist = [ + ('state', state), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.fake_servers[index].reset_state.assert_called_once_with( + state=state) + self.assertIsNone(result) + + def test_server_set_with_invalid_state(self): + arglist = [ + '--state', 'foo_state', + 'foo_vm', + ] + verifylist = [ + ('state', 'foo_state'), + ('server', 'foo_vm'), + ] + self.assertRaises(utils.ParserException, + self.check_parser, + self.cmd, arglist, verifylist) + + def test_server_set_with_name(self): + arglist = [ + '--name', 'foo_name', + 'foo_vm', + ] + verifylist = [ + ('name', 'foo_name'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.fake_servers[0].update.assert_called_once_with(name='foo_name') + self.assertIsNone(result) + + def test_server_set_with_property(self): + arglist = [ + '--property', 'key1=value1', + '--property', 'key2=value2', + 'foo_vm', + ] + verifylist = [ + ('property', {'key1': 'value1', 'key2': 'value2'}), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.servers_mock.set_meta.assert_called_once_with( + self.fake_servers[0], parsed_args.property) + self.assertIsNone(result) + + @mock.patch.object(getpass, 'getpass', + return_value=mock.sentinel.fake_pass) + def test_server_set_with_root_password(self, mock_getpass): + arglist = [ + '--root-password', + 'foo_vm', + ] + verifylist = [ + ('root_password', True), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.fake_servers[0].change_password.assert_called_once_with( + mock.sentinel.fake_pass) + self.assertIsNone(result) + + class TestServerShelve(TestServer): def setUp(self): @@ -1428,6 +1537,45 @@ class TestServerUnpause(TestServer): self.run_method_with_servers('unpause', 3) +class TestServerUnset(TestServer): + + def setUp(self): + super(TestServerUnset, self).setUp() + + self.fake_server = self.setup_servers_mock(1)[0] + + # Get the command object to test + self.cmd = server.UnsetServer(self.app, None) + + def test_server_unset_no_option(self): + arglist = [ + 'foo_vm', + ] + verifylist = [ + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.assertNotCalled(self.servers_mock.delete_meta) + self.assertIsNone(result) + + def test_server_unset_with_property(self): + arglist = [ + '--property', 'key1', + '--property', 'key2', + 'foo_vm', + ] + verifylist = [ + ('property', ['key1', 'key2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.servers_mock.delete_meta.assert_called_once_with( + self.fake_server, ['key1', 'key2']) + self.assertIsNone(result) + + class TestServerUnshelve(TestServer): def setUp(self): diff --git a/openstackclient/tests/compute/v2/test_server_backup.py b/openstackclient/tests/compute/v2/test_server_backup.py new file mode 100644 index 0000000..b35f9f5 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_server_backup.py @@ -0,0 +1,270 @@ +# 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.common import exceptions +from openstackclient.common 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 + + +class TestServerBackup(compute_fakes.TestComputev2): + + def setUp(self): + super(TestServerBackup, 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 overwriten in subclass. + self.attrs = {} + + # Set object methods to be tested. Could be overwriten 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 TestServerBackupCreate(TestServerBackup): + + # Just return whatever Image is testing with these days + 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(TestServerBackupCreate, self).setUp() + + # Get the command object to test + self.cmd = server_backup.CreateServerBackup(self.app, None) + + self.methods = { + 'backup': 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) + return images + + def test_server_backup_defaults(self): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + servers[0].id, + ] + verifylist = [ + ('name', None), + ('type', None), + ('rotate', None), + ('wait', False), + ('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.backup(server, backup_name, backup_type, rotation) + self.servers_mock.backup.assert_called_with( + servers[0].id, + servers[0].name, + '', + 1, + ) + + self.assertEqual(self.image_columns(images[0]), columns) + self.assertEqual(self.image_data(images[0]), data) + + def test_server_backup_create_options(self): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + '--name', 'image', + '--type', 'daily', + '--rotate', '2', + servers[0].id, + ] + verifylist = [ + ('name', 'image'), + ('type', 'daily'), + ('rotate', 2), + ('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.backup(server, backup_name, backup_type, rotation) + self.servers_mock.backup.assert_called_with( + servers[0].id, + 'image', + 'daily', + 2, + ) + + 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_backup_wait_fail(self, mock_wait_for_status): + servers = self.setup_servers_mock(count=1) + images = image_fakes.FakeImage.create_images( + attrs={ + 'name': servers[0].name, + 'status': 'active', + }, + count=5, + ) + + self.images_mock.get = mock.MagicMock( + side_effect=images, + ) + + arglist = [ + '--name', 'image', + '--type', 'daily', + '--wait', + servers[0].id, + ] + verifylist = [ + ('name', 'image'), + ('type', 'daily'), + ('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.backup(server, backup_name, backup_type, rotation) + self.servers_mock.backup.assert_called_with( + servers[0].id, + 'image', + 'daily', + 1, + ) + + 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_backup_wait_ok(self, mock_wait_for_status): + servers = self.setup_servers_mock(count=1) + images = image_fakes.FakeImage.create_images( + attrs={ + 'name': servers[0].name, + 'status': 'active', + }, + count=5, + ) + + self.images_mock.get = mock.MagicMock( + side_effect=images, + ) + + arglist = [ + '--name', 'image', + '--type', 'daily', + '--wait', + servers[0].id, + ] + verifylist = [ + ('name', 'image'), + ('type', 'daily'), + ('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.backup(server, backup_name, backup_type, rotation) + self.servers_mock.backup.assert_called_with( + servers[0].id, + 'image', + 'daily', + 1, + ) + + 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 7a5a840..cf53497 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -13,8 +13,7 @@ # under the License. # -import mock - +from openstackclient.common import exceptions from openstackclient.compute.v2 import service from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -128,6 +127,23 @@ class TestServiceSet(TestService): self.cmd = service.SetService(self.app, None) + def test_set_nothing(self): + arglist = [ + self.service.host, + self.service.binary, + ] + verifylist = [ + ('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_not_called() + self.service_mock.disable.assert_not_called() + self.service_mock.disable_log_reason.assert_not_called() + self.assertIsNone(result) + def test_service_set_enable(self): arglist = [ '--enable', @@ -135,7 +151,7 @@ class TestServiceSet(TestService): self.service.binary, ] verifylist = [ - ('enabled', True), + ('enable', True), ('host', self.service.host), ('service', self.service.binary), ] @@ -156,7 +172,7 @@ class TestServiceSet(TestService): self.service.binary, ] verifylist = [ - ('enabled', False), + ('disable', True), ('host', self.service.host), ('service', self.service.binary), ] @@ -179,7 +195,7 @@ class TestServiceSet(TestService): self.service.binary, ] verifylist = [ - ('enabled', False), + ('disable', True), ('disable_reason', reason), ('host', self.service.host), ('service', self.service.binary), @@ -203,24 +219,13 @@ class TestServiceSet(TestService): self.service.binary, ] verifylist = [ - ('enabled', True), ('disable_reason', reason), ('host', self.service.host), ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - with mock.patch.object(self.cmd.log, 'info') as mock_log: - result = self.cmd.take_action(parsed_args) - - msg = "argument --disable-reason has been ignored" - mock_log.assert_called_once_with(msg) - - self.service_mock.enable.assert_called_with( - self.service.host, - self.service.binary - ) - self.assertIsNone(result) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) def test_service_set_enable_with_disable_reason(self): reason = 'earthquake' @@ -231,21 +236,11 @@ class TestServiceSet(TestService): self.service.binary, ] verifylist = [ - ('enabled', True), + ('enable', True), ('disable_reason', reason), ('host', self.service.host), ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - with mock.patch.object(self.cmd.log, 'info') as mock_log: - result = self.cmd.take_action(parsed_args) - - msg = "argument --disable-reason has been ignored" - mock_log.assert_called_once_with(msg) - - self.service_mock.enable.assert_called_with( - self.service.host, - self.service.binary - ) - self.assertIsNone(result) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 229b465..ad4705a 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -97,6 +97,11 @@ class FakeApp(object): self.log = _log +class FakeOptions(object): + def __init__(self, **kwargs): + self.os_beta_command = False + + class FakeClient(object): def __init__(self, **kwargs): @@ -105,6 +110,9 @@ class FakeClient(object): class FakeClientManager(object): + _api_version = { + 'image': '2', + } def __init__(self): self.compute = None diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index a662a58..24aaec5 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -157,6 +157,8 @@ class FakeImagev2Client(object): self.images.resource_class = fakes.FakeResource(None, {}) self.image_members = mock.Mock() self.image_members.resource_class = fakes.FakeResource(None, {}) + self.image_tags = mock.Mock() + self.image_tags.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index beebdef..ca20d83 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -37,6 +37,8 @@ class TestImage(image_fakes.TestImagev2): self.images_mock.reset_mock() self.image_members_mock = self.app.client_manager.image.image_members self.image_members_mock.reset_mock() + self.image_tags_mock = self.app.client_manager.image.image_tags + self.image_tags_mock.reset_mock() # Get shortcut to the Mocks in identity client self.project_mock = self.app.client_manager.identity.projects @@ -1184,3 +1186,94 @@ class TestImageShow(TestImage): self.assertEqual(image_fakes.IMAGE_columns, columns) self.assertEqual(image_fakes.IMAGE_SHOW_data, data) + + +class TestImageUnset(TestImage): + + attrs = {} + attrs['tags'] = ['test'] + attrs['prop'] = 'test' + image = image_fakes.FakeImage.create_one_image(attrs) + + def setUp(self): + super(TestImageUnset, self).setUp() + + # Set up the schema + self.model = warlock.model_factory( + image_fakes.IMAGE_schema, + schemas.SchemaBasedModel, + ) + + self.images_mock.get.return_value = self.image + self.image_tags_mock.delete.return_value = self.image + + # Get the command object to test + self.cmd = image.UnsetImage(self.app, None) + + def test_image_unset_tag_option(self): + + arglist = [ + '--tag', 'test', + self.image.id, + ] + + verifylist = [ + ('tags', ['test']), + ('image', self.image.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.image_tags_mock.delete.assert_called_with( + self.image.id, 'test' + ) + self.assertIsNone(result) + + def test_image_unset_property_option(self): + + arglist = [ + '--property', 'prop', + self.image.id, + ] + + verifylist = [ + ('properties', ['prop']), + ('image', self.image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + kwargs = {} + self.images_mock.update.assert_called_with( + self.image.id, + parsed_args.properties, + **kwargs) + + self.assertIsNone(result) + + def test_image_unset_mixed_option(self): + + arglist = [ + '--tag', 'test', + '--property', 'prop', + self.image.id, + ] + + verifylist = [ + ('tags', ['test']), + ('properties', ['prop']), + ('image', self.image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + kwargs = {} + self.images_mock.update.assert_called_with( + self.image.id, + parsed_args.properties, + **kwargs) + + self.image_tags_mock.delete.assert_called_with( + self.image.id, 'test' + ) + self.assertIsNone(result) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 417cf26..ccbe395 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -127,6 +127,25 @@ class FakeAddressScope(object): return address_scopes + @staticmethod + def get_address_scopes(address_scopes=None, count=2): + """Get an iterable MagicMock object with a list of faked address scopes. + + If address scopes list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List address scopes: + A list of FakeResource objects faking address scopes + :param int count: + The number of address scopes to fake + :return: + An iterable Mock object with side_effect set to a list of faked + address scopes + """ + if address_scopes is None: + address_scopes = FakeAddressScope.create_address_scopes(count) + return mock.MagicMock(side_effect=address_scopes) + class FakeAvailabilityZone(object): """Fake one or more network availability zones (AZs).""" @@ -177,6 +196,50 @@ class FakeAvailabilityZone(object): return availability_zones +class FakeIPAvailability(object): + """Fake one or more network ip availabilities.""" + + @staticmethod + def create_one_ip_availability(): + """Create a fake list with ip availability stats of a network. + + :return: + A FakeResource object with network_name, network_id, etc. + """ + + # Set default attributes. + network_ip_availability = { + 'network_id': 'network-id-' + uuid.uuid4().hex, + 'network_name': 'network-name-' + uuid.uuid4().hex, + 'tenant_id': '', + 'subnet_ip_availability': [], + 'total_ips': 254, + 'used_ips': 6, + } + + network_ip_availability = fakes.FakeResource( + info=copy.deepcopy(network_ip_availability), + loaded=True) + return network_ip_availability + + @staticmethod + def create_ip_availability(count=2): + """Create fake list of ip availability stats of multiple networks. + + :param int count: + The number of networks to fake + :return: + A list of FakeResource objects faking network ip availability stats + """ + network_ip_availabilities = [] + for i in range(0, count): + network_ip_availability = \ + FakeIPAvailability.create_one_ip_availability() + network_ip_availabilities.append(network_ip_availability) + + return network_ip_availabilities + + class FakeNetwork(object): """Fake one or more networks.""" @@ -256,6 +319,58 @@ class FakeNetwork(object): return mock.MagicMock(side_effect=networks) +class FakeNetworkSegment(object): + """Fake one or more network segments.""" + + @staticmethod + def create_one_network_segment(attrs=None): + """Create a fake network segment. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object faking the network segment + """ + attrs = attrs or {} + + # Set default attributes. + network_segment_attrs = { + 'id': 'segment-id-' + uuid.uuid4().hex, + 'network_id': 'network-id-' + uuid.uuid4().hex, + 'network_type': 'vlan', + 'physical_network': 'physical-network-name-' + uuid.uuid4().hex, + 'segmentation_id': 1024, + } + + # Overwrite default attributes. + network_segment_attrs.update(attrs) + + network_segment = fakes.FakeResource( + info=copy.deepcopy(network_segment_attrs), + loaded=True + ) + + return network_segment + + @staticmethod + def create_network_segments(attrs=None, count=2): + """Create multiple fake network segments. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of network segments to fake + :return: + A list of FakeResource objects faking the network segments + """ + network_segments = [] + for i in range(0, count): + network_segments.append( + FakeNetworkSegment.create_one_network_segment(attrs) + ) + return network_segments + + class FakePort(object): """Fake one or more ports.""" diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index ac94b48..b4f4fa8 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -14,6 +14,7 @@ import copy import mock +from mock import call from openstackclient.common import exceptions from openstackclient.network.v2 import address_scope from openstackclient.tests import fakes @@ -168,33 +169,86 @@ class TestCreateAddressScope(TestAddressScope): class TestDeleteAddressScope(TestAddressScope): # The address scope to delete. - _address_scope = ( - network_fakes.FakeAddressScope.create_one_address_scope()) + _address_scopes = ( + network_fakes.FakeAddressScope.create_address_scopes(count=2)) def setUp(self): super(TestDeleteAddressScope, self).setUp() self.network.delete_address_scope = mock.Mock(return_value=None) - self.network.find_address_scope = mock.Mock( - return_value=self._address_scope) + self.network.find_address_scope = ( + network_fakes.FakeAddressScope.get_address_scopes( + address_scopes=self._address_scopes) + ) # Get the command object to test self.cmd = address_scope.DeleteAddressScope(self.app, self.namespace) - def test_delete(self): + def test_address_scope_delete(self): arglist = [ - self._address_scope.name, + self._address_scopes[0].name, ] verifylist = [ - ('address_scope', self._address_scope.name), + ('address_scope', [self._address_scopes[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) + self.network.find_address_scope.assert_called_once_with( + self._address_scopes[0].name, ignore_missing=False) self.network.delete_address_scope.assert_called_once_with( - self._address_scope) + self._address_scopes[0]) self.assertIsNone(result) + def test_multi_address_scopes_delete(self): + arglist = [] + verifylist = [] + + for a in self._address_scopes: + arglist.append(a.name) + verifylist = [ + ('address_scope', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._address_scopes: + calls.append(call(a)) + self.network.delete_address_scope.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_address_scopes_delete_with_exception(self): + arglist = [ + self._address_scopes[0].name, + 'unexist_address_scope', + ] + verifylist = [ + ('address_scope', + [self._address_scopes[0].name, 'unexist_address_scope']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._address_scopes[0], exceptions.CommandError] + self.network.find_address_scope = ( + 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 address scopes failed to delete.', str(e)) + + self.network.find_address_scope.assert_any_call( + self._address_scopes[0].name, ignore_missing=False) + self.network.find_address_scope.assert_any_call( + 'unexist_address_scope', ignore_missing=False) + self.network.delete_address_scope.assert_called_once_with( + self._address_scopes[0] + ) + class TestListAddressScope(TestAddressScope): diff --git a/openstackclient/tests/network/v2/test_ip_availability.py b/openstackclient/tests/network/v2/test_ip_availability.py new file mode 100644 index 0000000..04979e7 --- /dev/null +++ b/openstackclient/tests/network/v2/test_ip_availability.py @@ -0,0 +1,180 @@ +# 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 openstackclient.common import utils as osc_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 + + +class TestIPAvailability(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestIPAvailability, self).setUp() + + # 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, + ) + + +class TestListIPAvailability(TestIPAvailability): + + _ip_availability = \ + network_fakes.FakeIPAvailability.create_ip_availability(count=3) + columns = ( + 'Network ID', + 'Network Name', + 'Total IPs', + 'Used IPs', + ) + data = [] + for net in _ip_availability: + data.append(( + net.network_id, + net.network_name, + net.total_ips, + net.used_ips, + )) + + def setUp(self): + super(TestListIPAvailability, self).setUp() + + self.cmd = ip_availability.ListIPAvailability( + self.app, self.namespace) + self.network.network_ip_availabilities = mock.Mock( + return_value=self._ip_availability) + + def test_list_no_options(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.network_ip_availabilities.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_list_ip_version(self): + arglist = [ + '--ip-version', str(4), + ] + verifylist = [ + ('ip_version', 4) + ] + + 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( + **filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_list_project(self): + arglist = [ + '--project', identity_fakes.project_name + ] + verifylist = [ + ('project', identity_fakes.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} + + self.network.network_ip_availabilities.assert_called_once_with( + **filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowIPAvailability(TestIPAvailability): + + _ip_availability = \ + network_fakes.FakeIPAvailability.create_one_ip_availability() + + columns = ( + 'network_id', + 'network_name', + 'project_id', + 'subnet_ip_availability', + 'total_ips', + 'used_ips', + ) + data = ( + _ip_availability.network_id, + _ip_availability.network_name, + _ip_availability.tenant_id, + osc_utils.format_list( + _ip_availability.subnet_ip_availability), + _ip_availability.total_ips, + _ip_availability.used_ips, + ) + + def setUp(self): + super(TestShowIPAvailability, self).setUp() + + self.network.find_network_ip_availability = mock.Mock( + return_value=self._ip_availability) + + # Get the command object to test + self.cmd = ip_availability.ShowIPAvailability( + self.app, self.namespace) + + def test_show_no_option(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._ip_availability.network_name, + ] + verifylist = [ + ('network', self._ip_availability.network_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.network.find_network_ip_availability.assert_called_once_with( + self._ip_availability.network_name, + ignore_missing=False) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/network/v2/test_network_segment.py b/openstackclient/tests/network/v2/test_network_segment.py new file mode 100644 index 0000000..0a99ece --- /dev/null +++ b/openstackclient/tests/network/v2/test_network_segment.py @@ -0,0 +1,199 @@ +# 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.common 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 + + +class TestNetworkSegment(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestNetworkSegment, self).setUp() + + # Enable beta commands. + self.app.options.os_beta_command = True + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestListNetworkSegment(TestNetworkSegment): + _network = network_fakes.FakeNetwork.create_one_network() + _network_segments = \ + network_fakes.FakeNetworkSegment.create_network_segments(count=3) + + columns = ( + 'ID', + 'Network', + 'Network Type', + 'Segment', + ) + columns_long = columns + ( + 'Physical Network', + ) + + data = [] + for _network_segment in _network_segments: + data.append(( + _network_segment.id, + _network_segment.network_id, + _network_segment.network_type, + _network_segment.segmentation_id, + )) + + data_long = [] + for _network_segment in _network_segments: + data_long.append(( + _network_segment.id, + _network_segment.network_id, + _network_segment.network_type, + _network_segment.segmentation_id, + _network_segment.physical_network, + )) + + def setUp(self): + super(TestListNetworkSegment, self).setUp() + + # Get the command object to test + self.cmd = network_segment.ListNetworkSegment(self.app, self.namespace) + + self.network.find_network = mock.Mock(return_value=self._network) + self.network.segments = mock.Mock(return_value=self._network_segments) + + def test_list_no_option(self): + arglist = [] + verifylist = [ + ('long', False), + ('network', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.segments.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_list_no_beta_commands(self): + self.app.options.os_beta_command = False + parsed_args = self.check_parser(self.cmd, [], []) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ('network', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.segments.assert_called_once_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + def test_list_network(self): + arglist = [ + '--network', + self._network.id, + ] + verifylist = [ + ('long', False), + ('network', self._network.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.segments.assert_called_once_with( + **{'network_id': self._network.id} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowNetworkSegment(TestNetworkSegment): + + # The network segment to show. + _network_segment = \ + network_fakes.FakeNetworkSegment.create_one_network_segment() + + columns = ( + 'id', + 'network_id', + 'network_type', + 'physical_network', + 'segmentation_id', + ) + + data = ( + _network_segment.id, + _network_segment.network_id, + _network_segment.network_type, + _network_segment.physical_network, + _network_segment.segmentation_id, + ) + + def setUp(self): + super(TestShowNetworkSegment, self).setUp() + + self.network.find_segment = mock.Mock( + return_value=self._network_segment + ) + + # Get the command object to test + self.cmd = network_segment.ShowNetworkSegment(self.app, self.namespace) + + def test_show_no_options(self): + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, [], []) + + def test_show_no_beta_commands(self): + arglist = [ + self._network_segment.id, + ] + verifylist = [ + ('network_segment', self._network_segment.id), + ] + self.app.options.os_beta_command = False + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_show_all_options(self): + arglist = [ + self._network_segment.id, + ] + verifylist = [ + ('network_segment', self._network_segment.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_segment.assert_called_once_with( + self._network_segment.id, + ignore_missing=False + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index f2aa26c..dd98a63 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -13,6 +13,8 @@ import mock +from mock import call +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.network.v2 import port from openstackclient.tests.network.v2 import fakes as network_fakes @@ -174,30 +176,82 @@ class TestCreatePort(TestPort): class TestDeletePort(TestPort): - # The port to delete. - _port = network_fakes.FakePort.create_one_port() + # Ports to delete. + _ports = network_fakes.FakePort.create_ports(count=2) def setUp(self): super(TestDeletePort, self).setUp() self.network.delete_port = mock.Mock(return_value=None) - self.network.find_port = mock.Mock(return_value=self._port) + self.network.find_port = network_fakes.FakePort.get_ports( + ports=self._ports) # Get the command object to test self.cmd = port.DeletePort(self.app, self.namespace) - def test_delete(self): + def test_port_delete(self): arglist = [ - self._port.name, + self._ports[0].name, ] verifylist = [ - ('port', [self._port.name]), + ('port', [self._ports[0].name]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.find_port.assert_called_once_with( + self._ports[0].name, ignore_missing=False) + self.network.delete_port.assert_called_once_with(self._ports[0]) + self.assertIsNone(result) + + def test_multi_ports_delete(self): + arglist = [] + verifylist = [] + + for p in self._ports: + arglist.append(p.name) + verifylist = [ + ('port', arglist), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_port.assert_called_once_with(self._port) + + calls = [] + for p in self._ports: + calls.append(call(p)) + self.network.delete_port.assert_has_calls(calls) self.assertIsNone(result) + def test_multi_ports_delete_with_exception(self): + arglist = [ + self._ports[0].name, + 'unexist_port', + ] + verifylist = [ + ('port', + [self._ports[0].name, 'unexist_port']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._ports[0], exceptions.CommandError] + self.network.find_port = ( + 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 ports failed to delete.', str(e)) + + self.network.find_port.assert_any_call( + self._ports[0].name, ignore_missing=False) + self.network.find_port.assert_any_call( + 'unexist_port', ignore_missing=False) + self.network.delete_port.assert_called_once_with( + self._ports[0] + ) + class TestListPort(TestPort): @@ -283,6 +337,7 @@ class TestSetPort(TestPort): ] verifylist = [ ('fixed_ip', [{'ip-address': '10.0.0.11'}]), + ('port', self._port.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -304,6 +359,7 @@ class TestSetPort(TestPort): ] verifylist = [ ('fixed_ip', [{'ip-address': '10.0.0.12'}]), + ('port', _testport.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) @@ -325,6 +381,7 @@ class TestSetPort(TestPort): ('disable', True), ('no_binding_profile', True), ('no_fixed_ip', True), + ('port', self._port.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -352,7 +409,8 @@ class TestSetPort(TestPort): ('vnic_type', 'macvtap'), ('binding_profile', {'foo': 'bar'}), ('host', 'binding-host-id-xxxx'), - ('name', 'newName') + ('name', 'newName'), + ('port', self._port.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/utils.py b/openstackclient/tests/utils.py index 319c1c1..8dead71 100644 --- a/openstackclient/tests/utils.py +++ b/openstackclient/tests/utils.py @@ -60,6 +60,7 @@ class TestCommand(TestCase): self.fake_log = fakes.FakeLog() self.app = fakes.FakeApp(self.fake_stdout, self.fake_log) self.app.client_manager = fakes.FakeClientManager() + self.app.options = fakes.FakeOptions() def check_parser(self, cmd, args, verify_args): cmd_parser = cmd.get_parser('check_parser') diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/volume/v1/fakes.py index d6c4643..6c34986 100644 --- a/openstackclient/tests/volume/v1/fakes.py +++ b/openstackclient/tests/volume/v1/fakes.py @@ -129,6 +129,57 @@ QOS_WITH_ASSOCIATIONS = { } +class FakeTransferClient(object): + + def __init__(self, **kwargs): + + self.transfers = mock.Mock() + self.transfers.resource_class = fakes.FakeResource(None, {}) + + +class TestTransfer(utils.TestCommand): + + def setUp(self): + super(TestTransfer, self).setUp() + + self.app.client_manager.volume = FakeTransferClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + + +class FakeTransfer(object): + """Fake one or more Transfer.""" + + @staticmethod + def create_one_transfer(attrs=None): + """Create a fake transfer. + + :param Dictionary attrs: + A dictionary with all attributes of Transfer Request + :retrun: + A FakeResource object with volume_id, name, id. + """ + # Set default attribute + transfer_info = { + 'volume_id': 'ce26708d-a7f8-4b4b-9861-4a80256615a7', + 'name': 'fake_transfer_name', + 'id': '731a7f53-aa92-4fbd-9de3-6f7d729c926b' + } + + # Overwrite default attributes if there are some attributes set + attrs = attrs or {} + + transfer_info.update(attrs) + + transfer = fakes.FakeResource( + None, + transfer_info, + loaded=True) + + return transfer + + class FakeServiceClient(object): def __init__(self, **kwargs): @@ -171,8 +222,8 @@ class FakeService(object): } # Overwrite default attributes if there are some attributes set - if attrs is None: - attrs = {} + attrs = attrs or {} + service_info.update(attrs) service = fakes.FakeResource( diff --git a/openstackclient/tests/volume/v1/test_transfer_request.py b/openstackclient/tests/volume/v1/test_transfer_request.py new file mode 100644 index 0000000..94e02d6 --- /dev/null +++ b/openstackclient/tests/volume/v1/test_transfer_request.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. +# + + +from openstackclient.tests.volume.v1 import fakes as transfer_fakes +from openstackclient.volume.v1 import volume_transfer_request + + +class TestTransfer(transfer_fakes.TestTransfer): + + def setUp(self): + super(TestTransfer, self).setUp() + + # Get a shortcut to the TransferManager Mock + self.transfer_mock = self.app.client_manager.volume.transfers + self.transfer_mock.reset_mock() + + +class TestTransferList(TestTransfer): + + # The Transfers to be listed + volume_transfers = transfer_fakes.FakeTransfer.create_one_transfer() + + def setUp(self): + super(TestTransferList, self).setUp() + + self.transfer_mock.list.return_value = [self.volume_transfers] + + # Get the command object to test + self.cmd = volume_transfer_request.ListTransferRequests(self.app, None) + + def test_transfer_list_without_argument(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) + + expected_columns = [ + 'ID', + 'Volume', + 'Name', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.volume_transfers.id, + self.volume_transfers.volume_id, + self.volume_transfers.name, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list volume_transfers + self.transfer_mock.list.assert_called_with( + detailed=True, + search_opts={'all_tenants': 0} + ) + + def test_transfer_list_with_argument(self): + arglist = [ + "--all-projects" + ] + verifylist = [ + ("all_projects", 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) + + expected_columns = [ + 'ID', + 'Volume', + 'Name', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.volume_transfers.id, + self.volume_transfers.volume_id, + self.volume_transfers.name, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list volume_transfers + self.transfer_mock.list.assert_called_with( + detailed=True, + search_opts={'all_tenants': 1} + ) diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index e0fd1c0..e4f51bb 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -578,9 +578,6 @@ class TestVolumeSet(TestVolume): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - - self.assertEqual("No changes requested\n", - self.app.log.messages.get('error')) self.assertIsNone(result) def test_volume_set_name(self): diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 181552d..e61fe8a 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -232,6 +232,57 @@ EXTENSION = { } +class FakeTransferClient(object): + + def __init__(self, **kwargs): + + self.transfers = mock.Mock() + self.transfers.resource_class = fakes.FakeResource(None, {}) + + +class TestTransfer(utils.TestCommand): + + def setUp(self): + super(TestTransfer, self).setUp() + + self.app.client_manager.volume = FakeTransferClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + + +class FakeTransfer(object): + """Fake one or more Transfer.""" + + @staticmethod + def create_one_transfer(attrs=None): + """Create a fake transfer. + + :param Dictionary attrs: + A dictionary with all attributes of Transfer Request + :retrun: + A FakeResource object with volume_id, name, id. + """ + # Set default attribute + transfer_info = { + 'volume_id': 'ce26708d-a7f8-4b4b-9861-4a80256615a7', + 'name': 'fake_transfer_name', + 'id': '731a7f53-aa92-4fbd-9de3-6f7d729c926b' + } + + # Overwrite default attributes if there are some attributes set + attrs = attrs or {} + + transfer_info.update(attrs) + + transfer = fakes.FakeResource( + None, + transfer_info, + loaded=True) + + return transfer + + class FakeServiceClient(object): def __init__(self, **kwargs): @@ -274,8 +325,8 @@ class FakeService(object): } # Overwrite default attributes if there are some attributes set - if attrs is None: - attrs = {} + attrs = attrs or {} + service_info.update(attrs) service = fakes.FakeResource( @@ -592,6 +643,81 @@ class FakeBackup(object): return backups +class FakeQos(object): + """Fake one or more Qos specification.""" + + @staticmethod + def create_one_qos(attrs=None): + """Create a fake Qos specification. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, consumer, etc. + """ + attrs = attrs or {} + + # Set default attributes. + qos_info = { + "id": 'qos-id-' + uuid.uuid4().hex, + "name": 'qos-name-' + uuid.uuid4().hex, + "consumer": 'front-end', + "specs": {"foo": "bar", "iops": "9001"}, + } + + # Overwrite default attributes. + qos_info.update(attrs) + + qos = fakes.FakeResource( + info=copy.deepcopy(qos_info), + loaded=True) + return qos + + @staticmethod + def create_one_qos_association(attrs=None): + """Create a fake Qos specification association. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, association_type, etc. + """ + attrs = attrs or {} + + # Set default attributes. + qos_association_info = { + "id": 'type-id-' + uuid.uuid4().hex, + "name": 'type-name-' + uuid.uuid4().hex, + "association_type": 'volume_type', + } + + # Overwrite default attributes. + qos_association_info.update(attrs) + + qos_association = fakes.FakeResource( + info=copy.deepcopy(qos_association_info), + loaded=True) + return qos_association + + @staticmethod + def create_qoses(attrs=None, count=2): + """Create multiple fake Qos specifications. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of Qos specifications to fake + :return: + A list of FakeResource objects faking the Qos specifications + """ + qoses = [] + for i in range(0, count): + qos = FakeQos.create_one_qos(attrs) + qoses.append(qos) + + return qoses + + class FakeSnapshot(object): """Fake one or more snapshot.""" diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py index 5232285..741f4e7 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -13,10 +13,7 @@ # under the License. # -import copy - from openstackclient.common import utils -from openstackclient.tests import fakes from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import qos_specs @@ -35,275 +32,228 @@ class TestQos(volume_fakes.TestVolume): class TestQosAssociate(TestQos): + volume_type = volume_fakes.FakeType.create_one_type() + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosAssociate, self).setUp() + self.qos_mock.get.return_value = self.qos_spec + self.types_mock.get.return_value = self.volume_type # Get the command object to test self.cmd = qos_specs.AssociateQos(self.app, None) def test_qos_associate(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - self.types_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True - ) arglist = [ - volume_fakes.qos_id, - volume_fakes.type_id + self.qos_spec.id, + self.volume_type.id ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('volume_type', volume_fakes.type_id) + ('qos_spec', self.qos_spec.id), + ('volume_type', self.volume_type.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.associate.assert_called_with( - volume_fakes.qos_id, - volume_fakes.type_id + self.qos_spec.id, + self.volume_type.id ) self.assertIsNone(result) class TestQosCreate(TestQos): + new_qos_spec = volume_fakes.FakeQos.create_one_qos() columns = ( 'consumer', 'id', - 'name' + 'name', + 'specs' ) - datalist = ( - volume_fakes.qos_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name + data = ( + new_qos_spec.consumer, + new_qos_spec.id, + new_qos_spec.name, + new_qos_spec.specs ) def setUp(self): super(TestQosCreate, self).setUp() + self.qos_mock.create.return_value = self.new_qos_spec # Get the command object to test self.cmd = qos_specs.CreateQos(self.app, None) def test_qos_create_without_properties(self): - self.qos_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_DEFAULT_CONSUMER), - loaded=True - ) - arglist = [ - volume_fakes.qos_name, + self.new_qos_spec.name, ] verifylist = [ - ('name', volume_fakes.qos_name), + ('name', self.new_qos_spec.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.qos_mock.create.assert_called_with( - volume_fakes.qos_name, - {'consumer': volume_fakes.qos_default_consumer} + self.new_qos_spec.name, + {'consumer': 'both'} ) self.assertEqual(self.columns, columns) - datalist = ( - volume_fakes.qos_default_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name - ) - self.assertEqual(datalist, data) + self.assertEqual(self.data, data) def test_qos_create_with_consumer(self): - self.qos_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - arglist = [ - volume_fakes.qos_name, - '--consumer', volume_fakes.qos_consumer + '--consumer', self.new_qos_spec.consumer, + self.new_qos_spec.name, ] verifylist = [ - ('name', volume_fakes.qos_name), - ('consumer', volume_fakes.qos_consumer) + ('consumer', self.new_qos_spec.consumer), + ('name', self.new_qos_spec.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.qos_mock.create.assert_called_with( - volume_fakes.qos_name, - {'consumer': volume_fakes.qos_consumer} + self.new_qos_spec.name, + {'consumer': self.new_qos_spec.consumer} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertEqual(self.data, data) def test_qos_create_with_properties(self): - self.qos_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_SPECS), - loaded=True - ) - arglist = [ - volume_fakes.qos_name, - '--consumer', volume_fakes.qos_consumer, + '--consumer', self.new_qos_spec.consumer, '--property', 'foo=bar', - '--property', 'iops=9001' + '--property', 'iops=9001', + self.new_qos_spec.name, ] verifylist = [ - ('name', volume_fakes.qos_name), - ('consumer', volume_fakes.qos_consumer), - ('property', volume_fakes.qos_specs) + ('consumer', self.new_qos_spec.consumer), + ('property', self.new_qos_spec.specs), + ('name', self.new_qos_spec.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - specs = volume_fakes.qos_specs.copy() - specs.update({'consumer': volume_fakes.qos_consumer}) + self.new_qos_spec.specs.update( + {'consumer': self.new_qos_spec.consumer}) self.qos_mock.create.assert_called_with( - volume_fakes.qos_name, - specs + self.new_qos_spec.name, + self.new_qos_spec.specs ) - columns = self.columns + ( - 'specs', - ) - self.assertEqual(columns, columns) - datalist = self.datalist + ( - volume_fakes.qos_specs, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) class TestQosDelete(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosDelete, self).setUp() - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True, - ) - + self.qos_mock.get.return_value = self.qos_spec # Get the command object to test self.cmd = qos_specs.DeleteQos(self.app, None) - def test_qos_delete_with_id(self): - arglist = [ - volume_fakes.qos_id - ] - verifylist = [ - ('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) - self.assertIsNone(result) - - def test_qos_delete_with_name(self): + def test_qos_delete(self): arglist = [ - volume_fakes.qos_name + self.qos_spec.id ] verifylist = [ - ('qos_specs', [volume_fakes.qos_name]) + ('qos_specs', [self.qos_spec.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) + self.qos_mock.delete.assert_called_with(self.qos_spec.id) self.assertIsNone(result) class TestQosDisassociate(TestQos): + volume_type = volume_fakes.FakeType.create_one_type() + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosDisassociate, self).setUp() + self.qos_mock.get.return_value = self.qos_spec + self.types_mock.get.return_value = self.volume_type # Get the command object to test self.cmd = qos_specs.DisassociateQos(self.app, None) def test_qos_disassociate_with_volume_type(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - self.types_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True - ) arglist = [ - volume_fakes.qos_id, - '--volume-type', volume_fakes.type_id + '--volume-type', self.volume_type.id, + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('volume_type', volume_fakes.type_id) + ('volume_type', self.volume_type.id), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.disassociate.assert_called_with( - volume_fakes.qos_id, - volume_fakes.type_id + self.qos_spec.id, + self.volume_type.id ) self.assertIsNone(result) def test_qos_disassociate_with_all_volume_types(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - arglist = [ - volume_fakes.qos_id, - '--all' + '--all', + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id) + ('qos_spec', self.qos_spec.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.disassociate_all.assert_called_with(volume_fakes.qos_id) + self.qos_mock.disassociate_all.assert_called_with(self.qos_spec.id) self.assertIsNone(result) class TestQosList(TestQos): + qos_specs = volume_fakes.FakeQos.create_qoses(count=2) + qos_association = volume_fakes.FakeQos.create_one_qos_association() + + columns = ( + 'ID', + 'Name', + 'Consumer', + 'Associations', + 'Specs', + ) + data = [] + for q in qos_specs: + data.append(( + q.id, + q.name, + q.consumer, + qos_association.name, + utils.format_dict(q.specs), + )) + def setUp(self): super(TestQosList, self).setUp() - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS), - loaded=True, - ) - self.qos_mock.list.return_value = [self.qos_mock.get.return_value] - self.qos_mock.get_associations.return_value = [fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.qos_association), - loaded=True, - )] + self.qos_mock.list.return_value = self.qos_specs + self.qos_mock.get_associations.return_value = [self.qos_association] # Get the command object to test self.cmd = qos_specs.ListQos(self.app, None) @@ -317,139 +267,117 @@ class TestQosList(TestQos): columns, data = self.cmd.take_action(parsed_args) self.qos_mock.list.assert_called_with() - collist = ( - 'ID', - 'Name', - 'Consumer', - 'Associations', - 'Specs', - ) - self.assertEqual(collist, columns) - datalist = (( - volume_fakes.qos_id, - volume_fakes.qos_name, - volume_fakes.qos_consumer, - volume_fakes.type_name, - utils.format_dict(volume_fakes.qos_specs), - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) class TestQosSet(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosSet, self).setUp() + self.qos_mock.get.return_value = self.qos_spec # Get the command object to test self.cmd = qos_specs.SetQos(self.app, None) def test_qos_set_with_properties_with_id(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_SPECS), - loaded=True - ) arglist = [ - volume_fakes.qos_id, '--property', 'foo=bar', - '--property', 'iops=9001' + '--property', 'iops=9001', + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('property', volume_fakes.qos_specs) + ('property', self.qos_spec.specs), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.set_keys.assert_called_with( - volume_fakes.qos_id, - volume_fakes.qos_specs + self.qos_spec.id, + self.qos_spec.specs ) self.assertIsNone(result) class TestQosShow(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + qos_association = volume_fakes.FakeQos.create_one_qos_association() + + columns = ( + 'associations', + 'consumer', + 'id', + 'name', + 'specs' + ) + data = ( + qos_association.name, + qos_spec.consumer, + qos_spec.id, + qos_spec.name, + utils.format_dict(qos_spec.specs), + ) + def setUp(self): super(TestQosShow, self).setUp() - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS), - loaded=True, - ) - self.qos_mock.get_associations.return_value = [fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.qos_association), - loaded=True, - )] + self.qos_mock.get.return_value = self.qos_spec + self.qos_mock.get_associations.return_value = [self.qos_association] # Get the command object to test self.cmd = qos_specs.ShowQos(self.app, None) def test_qos_show(self): arglist = [ - volume_fakes.qos_id + self.qos_spec.id ] verifylist = [ - ('qos_spec', volume_fakes.qos_id) + ('qos_spec', self.qos_spec.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.qos_mock.get.assert_called_with( - volume_fakes.qos_id + self.qos_spec.id ) - collist = ( - 'associations', - 'consumer', - 'id', - 'name', - 'specs' - ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.type_name, - volume_fakes.qos_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name, - utils.format_dict(volume_fakes.qos_specs), - ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, tuple(data)) class TestQosUnset(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosUnset, self).setUp() + self.qos_mock.get.return_value = self.qos_spec # Get the command object to test self.cmd = qos_specs.UnsetQos(self.app, None) def test_qos_unset_with_properties(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) arglist = [ - volume_fakes.qos_id, '--property', 'iops', - '--property', 'foo' + '--property', 'foo', + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('property', ['iops', 'foo']) + ('property', ['iops', 'foo']), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.unset_keys.assert_called_with( - volume_fakes.qos_id, + self.qos_spec.id, ['iops', 'foo'] ) self.assertIsNone(result) diff --git a/openstackclient/tests/volume/v2/test_transfer_request.py b/openstackclient/tests/volume/v2/test_transfer_request.py new file mode 100644 index 0000000..945833c --- /dev/null +++ b/openstackclient/tests/volume/v2/test_transfer_request.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. +# + + +from openstackclient.tests.volume.v2 import fakes as transfer_fakes +from openstackclient.volume.v2 import volume_transfer_request + + +class TestTransfer(transfer_fakes.TestTransfer): + + def setUp(self): + super(TestTransfer, self).setUp() + + # Get a shortcut to the TransferManager Mock + self.transfer_mock = self.app.client_manager.volume.transfers + self.transfer_mock.reset_mock() + + +class TestTransferList(TestTransfer): + + # The Transfers to be listed + volume_transfers = transfer_fakes.FakeTransfer.create_one_transfer() + + def setUp(self): + super(TestTransferList, self).setUp() + + self.transfer_mock.list.return_value = [self.volume_transfers] + + # Get the command object to test + self.cmd = volume_transfer_request.ListTransferRequests(self.app, None) + + def test_transfer_list_without_argument(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) + + expected_columns = [ + 'ID', + 'Volume', + 'Name', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.volume_transfers.id, + self.volume_transfers.volume_id, + self.volume_transfers.name, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list volume_transfers + self.transfer_mock.list.assert_called_with( + detailed=True, + search_opts={'all_tenants': 0} + ) + + def test_transfer_list_with_argument(self): + arglist = [ + "--all-projects" + ] + verifylist = [ + ("all_projects", 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) + + expected_columns = [ + 'ID', + 'Volume', + 'Name', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.volume_transfers.id, + self.volume_transfers.volume_id, + self.volume_transfers.name, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list volume_transfers + self.transfer_mock.list.assert_called_with( + detailed=True, + search_opts={'all_tenants': 1} + ) diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 872b4ae..10c3861 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -397,17 +397,19 @@ class TestTypeUnset(TestType): def test_type_unset(self): arglist = [ '--property', 'property', + '--property', 'multi_property', self.volume_type.id, ] verifylist = [ - ('property', 'property'), + ('property', ['property', 'multi_property']), ('volume_type', self.volume_type.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.volume_type.unset_keys.assert_called_once_with('property') + self.volume_type.unset_keys.assert_called_once_with( + ['property', 'multi_property']) self.assertIsNone(result) def test_type_unset_project_access(self): diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 85ff614..fb48d8a 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -14,12 +14,12 @@ import copy -import mock from mock import call from openstackclient.common import 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.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume @@ -301,19 +301,16 @@ class TestVolumeCreate(TestVolume): self.assertEqual(self.datalist, data) def test_volume_create_image_id(self): - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.IMAGE), - loaded=True, - ) + image = image_fakes.FakeImage.create_one_image() + self.images_mock.get.return_value = image arglist = [ - '--image', volume_fakes.image_id, + '--image', image.id, '--size', str(self.new_volume.size), self.new_volume.name, ] verifylist = [ - ('image', volume_fakes.image_id), + ('image', image.id), ('size', self.new_volume.size), ('name', self.new_volume.name), ] @@ -334,7 +331,7 @@ class TestVolumeCreate(TestVolume): project_id=None, availability_zone=None, metadata=None, - imageRef=volume_fakes.image_id, + imageRef=image.id, source_volid=None, ) @@ -342,19 +339,16 @@ class TestVolumeCreate(TestVolume): self.assertEqual(self.datalist, data) def test_volume_create_image_name(self): - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.IMAGE), - loaded=True, - ) + image = image_fakes.FakeImage.create_one_image() + self.images_mock.get.return_value = image arglist = [ - '--image', volume_fakes.image_name, + '--image', image.name, '--size', str(self.new_volume.size), self.new_volume.name, ] verifylist = [ - ('image', volume_fakes.image_name), + ('image', image.name), ('size', self.new_volume.size), ('name', self.new_volume.name), ] @@ -375,7 +369,7 @@ class TestVolumeCreate(TestVolume): project_id=None, availability_zone=None, metadata=None, - imageRef=volume_fakes.image_id, + imageRef=image.id, source_volid=None ) @@ -383,21 +377,21 @@ class TestVolumeCreate(TestVolume): self.assertEqual(self.datalist, data) def test_volume_create_with_snapshot(self): + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + self.new_volume.snapshot_id = snapshot.id arglist = [ '--size', str(self.new_volume.size), - '--snapshot', volume_fakes.snapshot_id, + '--snapshot', self.new_volume.snapshot_id, self.new_volume.name, ] verifylist = [ ('size', self.new_volume.size), - ('snapshot', volume_fakes.snapshot_id), + ('snapshot', self.new_volume.snapshot_id), ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - fake_snapshot = mock.Mock() - fake_snapshot.id = volume_fakes.snapshot_id - self.snapshots_mock.get.return_value = fake_snapshot + self.snapshots_mock.get.return_value = snapshot # 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 @@ -406,7 +400,7 @@ class TestVolumeCreate(TestVolume): self.volumes_mock.create.assert_called_once_with( size=self.new_volume.size, - snapshot_id=fake_snapshot.id, + snapshot_id=snapshot.id, name=self.new_volume.name, description=None, volume_type=None, @@ -642,12 +636,12 @@ class TestVolumeList(TestVolume): def test_volume_list_name(self): arglist = [ - '--name', volume_fakes.volume_name, + '--name', self.mock_volume.name, ] verifylist = [ ('long', False), ('all_projects', False), - ('name', volume_fakes.volume_name), + ('name', self.mock_volume.name), ('status', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -670,13 +664,13 @@ class TestVolumeList(TestVolume): def test_volume_list_status(self): arglist = [ - '--status', volume_fakes.volume_status, + '--status', self.mock_volume.status, ] verifylist = [ ('long', False), ('all_projects', False), ('name', None), - ('status', volume_fakes.volume_status), + ('status', self.mock_volume.status), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index b9eb875..c49477a 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -202,8 +202,6 @@ class SetQos(command.Command): if parsed_args.property: volume_client.qos_specs.set_keys(qos_spec.id, parsed_args.property) - else: - self.app.log.error(_("No changes requested\n")) class ShowQos(command.ShowOne): @@ -263,5 +261,3 @@ class UnsetQos(command.Command): if parsed_args.property: volume_client.qos_specs.unset_keys(qos_spec.id, parsed_args.property) - else: - self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index bf5bf26..5132d71 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -208,10 +208,6 @@ class SetSnapshot(command.Command): if parsed_args.description: kwargs['display_description'] = parsed_args.description - if not kwargs and not parsed_args.property: - self.app.log.error(_("No changes requested\n")) - return - snapshot.update(**kwargs) @@ -270,5 +266,3 @@ class UnsetSnapshot(command.Command): snapshot.id, parsed_args.property, ) - else: - self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 11e42f8..ffec180 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -363,9 +363,6 @@ class SetVolume(command.Command): if kwargs: volume_client.volumes.update(volume.id, **kwargs) - if not kwargs and not parsed_args.property and not parsed_args.size: - self.app.log.error(_("No changes requested\n")) - class ShowVolume(command.ShowOne): """Show volume details""" @@ -428,5 +425,3 @@ class UnsetVolume(command.Command): volume.id, parsed_args.property, ) - else: - self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py new file mode 100644 index 0000000..98689e7 --- /dev/null +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -0,0 +1,51 @@ +# +# 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. +# + +"""Volume v2 transfer action implementations""" + + +from openstackclient.common import command +from openstackclient.common import utils +from openstackclient.i18n import _ + + +class ListTransferRequests(command.Lister): + """Lists all volume transfer requests.""" + + def get_parser(self, prog_name): + parser = super(ListTransferRequests, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + dest='all_projects', + action="store_true", + default=False, + help=_('Shows detail for all projects. Admin only. ' + '(defaults to False)') + ) + return parser + + def take_action(self, parsed_args): + columns = ['ID', 'Volume ID', 'Name'] + column_headers = ['ID', 'Volume', 'Name'] + + volume_client = self.app.client_manager.volume + + volume_transfer_result = volume_client.transfers.list( + detailed=True, + search_opts={'all_tenants': parsed_args.all_projects} + ) + + return (column_headers, ( + utils.get_item_properties(s, columns) + for s in volume_transfer_result)) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 7392702..4e9b192 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -181,5 +181,3 @@ class UnsetVolumeType(command.Command): if parsed_args.property: volume_type.unset_keys(parsed_args.property) - else: - self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index aa2059d..90e11c7 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -202,8 +202,6 @@ class SetQos(command.Command): if parsed_args.property: volume_client.qos_specs.set_keys(qos_spec.id, parsed_args.property) - else: - self.app.log.error(_("No changes requested\n")) class ShowQos(command.ShowOne): @@ -250,8 +248,8 @@ class UnsetQos(command.Command): metavar='<key>', action='append', default=[], - help=('Property to remove from the QoS specification. ' - '(repeat option to unset multiple properties)'), + help=_('Property to remove from the QoS specification. ' + '(repeat option to unset multiple properties)'), ) return parser @@ -263,5 +261,3 @@ class UnsetQos(command.Command): if parsed_args.property: volume_client.qos_specs.unset_keys(qos_spec.id, parsed_args.property) - else: - self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index db4ce6c..d901708 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -37,7 +37,7 @@ class CreateSnapshot(command.ShowOne): parser.add_argument( "--name", metavar="<name>", - help=("Name of the snapshot") + help=_("Name of the snapshot") ) parser.add_argument( "--description", @@ -205,11 +205,6 @@ class SetSnapshot(command.Command): if parsed_args.description: kwargs['description'] = parsed_args.description - if (not kwargs and not parsed_args.property and not - parsed_args.state): - self.app.log.error(_("No changes requested\n")) - return - if parsed_args.property: volume_client.volume_snapshots.set_metadata(snapshot.id, parsed_args.property) @@ -271,5 +266,3 @@ class UnsetSnapshot(command.Command): snapshot.id, parsed_args.property, ) - else: - self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 0e07aa7..18473da 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -385,10 +385,6 @@ class SetVolume(command.Command): if kwargs: volume_client.volumes.update(volume.id, **kwargs) - if (not kwargs and not parsed_args.property - and not parsed_args.image_property and not parsed_args.size): - self.app.log.error(_("No changes requested\n")) - class ShowVolume(command.ShowOne): """Display volume details""" diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py new file mode 100644 index 0000000..98689e7 --- /dev/null +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -0,0 +1,51 @@ +# +# 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. +# + +"""Volume v2 transfer action implementations""" + + +from openstackclient.common import command +from openstackclient.common import utils +from openstackclient.i18n import _ + + +class ListTransferRequests(command.Lister): + """Lists all volume transfer requests.""" + + def get_parser(self, prog_name): + parser = super(ListTransferRequests, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + dest='all_projects', + action="store_true", + default=False, + help=_('Shows detail for all projects. Admin only. ' + '(defaults to False)') + ) + return parser + + def take_action(self, parsed_args): + columns = ['ID', 'Volume ID', 'Name'] + column_headers = ['ID', 'Volume', 'Name'] + + volume_client = self.app.client_manager.volume + + volume_transfer_result = volume_client.transfers.list( + detailed=True, + search_opts={'all_tenants': parsed_args.all_projects} + ) + + return (column_headers, ( + utils.get_item_properties(s, columns) + for s in volume_transfer_result)) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index adaccb0..9aed17b 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -176,13 +176,6 @@ class SetVolumeType(command.Command): volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) - if (not parsed_args.name - and not parsed_args.description - and not parsed_args.property - and not parsed_args.project): - self.app.log.error(_("No changes requested\n")) - return - result = 0 kwargs = {} if parsed_args.name: @@ -205,8 +198,8 @@ class SetVolumeType(command.Command): try: volume_type.set_keys(parsed_args.property) except Exception as e: - self.app.log.error(_("Failed to set volume type property: ") + - str(e)) + self.app.log.error(_("Failed to set volume type" + " property: %s") % str(e)) result += 1 if parsed_args.project: @@ -225,8 +218,8 @@ class SetVolumeType(command.Command): result += 1 if result > 0: - raise exceptions.CommandError("Command Failed: One or more of the" - " operations failed") + raise exceptions.CommandError(_("Command Failed: One or more of" + " the operations failed")) class ShowVolumeType(command.ShowOne): @@ -263,6 +256,7 @@ class UnsetVolumeType(command.Command): parser.add_argument( '--property', metavar='<key>', + action='append', help=_('Remove a property from this volume type ' '(repeat option to remove multiple properties)'), ) @@ -285,11 +279,6 @@ class UnsetVolumeType(command.Command): parsed_args.volume_type, ) - if (not parsed_args.property - and not parsed_args.project): - self.app.log.error(_("No changes requested\n")) - return - result = 0 if parsed_args.property: try: @@ -311,9 +300,9 @@ class UnsetVolumeType(command.Command): volume_type.id, project_info.id) except Exception as e: self.app.log.error(_("Failed to remove volume type access from" - " project: ") + str(e)) + " project: %s") % str(e)) result += 1 if result > 0: - raise exceptions.CommandError("Command Failed: One or more of the" - " operations failed") + raise exceptions.CommandError(_("Command Failed: One or more of" + " the operations failed")) diff --git a/releasenotes/notes/add-server-backup-e63feaebb6140f83.yaml b/releasenotes/notes/add-server-backup-e63feaebb6140f83.yaml new file mode 100644 index 0000000..670f506 --- /dev/null +++ b/releasenotes/notes/add-server-backup-e63feaebb6140f83.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add ``server backup create`` command diff --git a/releasenotes/notes/address-scope-delete-multi-31c3af73feb31265.yaml b/releasenotes/notes/address-scope-delete-multi-31c3af73feb31265.yaml new file mode 100644 index 0000000..91bce23 --- /dev/null +++ b/releasenotes/notes/address-scope-delete-multi-31c3af73feb31265.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + ``address scope delete`` command now accepts multiple address scopes + in a single command diff --git a/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml b/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml new file mode 100644 index 0000000..9157958 --- /dev/null +++ b/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add ``network segment list`` and ``network segment show`` commands. + These are beta commands and subject to change. Use global option + ``--os-beta-command`` to enable these commands. + [Blueprint `routed-networks <https://blueprints.launchpad.net/neutron/+spec/routed-networks>`_] diff --git a/releasenotes/notes/bug-1554886-8e5249a655e7e7b6.yaml b/releasenotes/notes/bug-1554886-8e5249a655e7e7b6.yaml new file mode 100644 index 0000000..bafb87d --- /dev/null +++ b/releasenotes/notes/bug-1554886-8e5249a655e7e7b6.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``volume transfer request list`` command + [:lpbug:`1554886`] diff --git a/releasenotes/notes/bug-1575461-4d7d90e792132064.yaml b/releasenotes/notes/bug-1575461-4d7d90e792132064.yaml new file mode 100644 index 0000000..3ae0075 --- /dev/null +++ b/releasenotes/notes/bug-1575461-4d7d90e792132064.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Add ``--project`` option to ``flavor set`` command to set project access + to a flavor + [:lpbug:`1575461`] + - | + Add ``--project`` option to ``flavor unset`` command to remove project access + to a flavor + [:lpbug:`1575461`] diff --git a/releasenotes/notes/bug-1582968-4d44912a033b242c.yaml b/releasenotes/notes/bug-1582968-4d44912a033b242c.yaml new file mode 100644 index 0000000..9fd50c1 --- /dev/null +++ b/releasenotes/notes/bug-1582968-4d44912a033b242c.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``image unset`` command + [:lpbug:`1582968`] diff --git a/releasenotes/notes/bug-1588384-eb6976fcfb90cb4c.yaml b/releasenotes/notes/bug-1588384-eb6976fcfb90cb4c.yaml new file mode 100644 index 0000000..797d673 --- /dev/null +++ b/releasenotes/notes/bug-1588384-eb6976fcfb90cb4c.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - Fix the ``--enable`` option on all commands by changing the + ``--enable-beta-commands`` global option to ``--os-beta-command``. + There are no upgrade impacts for the global option rename since + the old name isn't used. + [Bug `1588384 <https://bugs.launchpad.net/python-openstackclient/+bug/1588384>`_] diff --git a/releasenotes/notes/compute-agent-deff48988e81b30e.yaml b/releasenotes/notes/compute-agent-deff48988e81b30e.yaml new file mode 100644 index 0000000..219ef96 --- /dev/null +++ b/releasenotes/notes/compute-agent-deff48988e81b30e.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + ``compute agent delete`` command now supports deleting multiple agents + in a single command diff --git a/releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml b/releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml new file mode 100644 index 0000000..2c78388 --- /dev/null +++ b/releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``ip availability list`` and ``ip availability show`` commands + [Blueprint :oscbp:`neutron-ip-capacity`] diff --git a/releasenotes/notes/server-set-state-214b12ec2161de4d.yaml b/releasenotes/notes/server-set-state-214b12ec2161de4d.yaml new file mode 100644 index 0000000..a48384a --- /dev/null +++ b/releasenotes/notes/server-set-state-214b12ec2161de4d.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--state`` option to ``server set`` command to set the server to + active or error state. + [Blueprint `server-reset-state <https://blueprints.launchpad.net/python-openstackclient/+spec/server-reset-state>`_] diff --git a/releasenotes/notes/service-set-option-61772a8940ad0778.yaml b/releasenotes/notes/service-set-option-61772a8940ad0778.yaml new file mode 100644 index 0000000..02c7fcb --- /dev/null +++ b/releasenotes/notes/service-set-option-61772a8940ad0778.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - An exception is not raised by command ``service set`` when nothing + specified. Instead, the service is not enabled by default. And if + ``--disable-resion`` is specified but not ``--disable``, an + exception will be raised. diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 1b6929e..12ed68d 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -40,8 +40,28 @@ extensions = [ 'oslosphinx', 'reno.sphinxext', + 'sphinx.ext.extlinks', ] +# Set aliases for extlinks +# * lpbug - generic Launchpad bug :lpbug:`123456` +# * oscbp - OSC blueprints :oscbp:`Blue Print <bp-name>` +# * oscdoc - OSC Docs :oscdoc:`Comamnd List <command-list>` +extlinks = { + 'lpbug': ( + 'https://bugs.launchpad.net/bugs/%s', + 'Bug ', + ), + 'oscbp': ( + 'https://blueprints.launchpad.net/python-openstackclient/+spec/%s', + '', + ), + 'oscdoc': ( + 'http://docs.openstack.org/developer/python-openstackclient/%s.html', + '', + ), +} + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index e3af0e6..e0477df 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -7,3 +7,4 @@ OpenStackClient Release Notes unreleased mitaka + pre_20_releases diff --git a/releasenotes/source/mitaka.rst b/releasenotes/source/mitaka.rst index e545609..05ed3e4 100644 --- a/releasenotes/source/mitaka.rst +++ b/releasenotes/source/mitaka.rst @@ -1,6 +1,6 @@ -=================================== - Mitaka Series Release Notes -=================================== +=========================== +Mitaka Series Release Notes +=========================== .. release-notes:: :branch: origin/stable/mitaka diff --git a/doc/source/releases.rst b/releasenotes/source/pre_20_releases.rst index 063066a..a2af7a9 100644 --- a/doc/source/releases.rst +++ b/releasenotes/source/pre_20_releases.rst @@ -1,16 +1,12 @@ -============= -Release Notes -============= - -As of release 2.0 the release notes can be found on the OpenStack `Release Notes site`_. - -.. _`Release Notes site`: http://docs.openstack.org/releasenotes/python-openstackclient +================ +Pre-2.0 Releases +================ 1.9.0 (17 Nov 2015) =================== * Several updates to `openstack server` - Blueprint `servers <https://blueprints.launchpad.net/python-openstackclient/+spec/cmd-with-multi-servers>`_ + Blueprint :oscbp:`servers <cmd-with-multi-servers>` * `openstack server start` * `openstack server stop` @@ -365,17 +361,17 @@ As of release 2.0 the release notes can be found on the OpenStack `Release Notes Bug `1446751 <https://bugs.launchpad.net/bugs/1446751>`_ * Document the backward-compatibility-breaking changes in - :doc:`backwards-incompatible`. - Bug `1406470 <https://bugs.launchpad.net/bugs/1406470>`_ + :oscdoc:`Backwards Incompatible Changes <backwards-incompatible>`. + :lpbug:`1406470` -* Add `--parent`` option to `projct create` command. +* Add ``--parent`` option to ``project create`` command 1.1.0 (21 Apr 2015) =================== * Add global ``--os-cloud`` option to select from a list of cloud configurations. - See :doc:`configuration` for more details. + See :oscdoc:`Configuration <configuration>` for more details. * Fix global ``--timing`` option operation. Bug `1402577 <https://bugs.launchpad.net/bugs/1402577>`_ @@ -463,8 +459,9 @@ As of release 2.0 the release notes can be found on the OpenStack `Release Notes =================== * The OpenStackClient content from the OpenStack Wiki has been migrated into - the OSC source repo. This includes the :doc:`commands`, :doc:`command-list` - and :doc:`humaninterfaceguide` documents. + the OSC source repo. This includes the :oscdoc:`Command Structure <commands>`, + :oscdoc:`Command List <command-list>` and + :oscdoc:`Human Interface Guide <humaninterfaceguide>` documents. * Set a default domain ID when both ``OS_USER_DOMAIN_ID`` and ``OS_USER_DOMAIN_NAME`` are not set. This is also done for diff --git a/requirements.txt b/requirements.txt index 62bcfe3..5d8c844 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,12 +9,12 @@ 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.9.0 # Apache-2.0 +oslo.config>=3.10.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.utils>=3.5.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 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 -python-cinderclient!=1.7.0,>=1.6.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 @@ -131,6 +131,8 @@ openstack.compute.v2 = server_unset = openstackclient.compute.v2.server:UnsetServer server_unshelve = openstackclient.compute.v2.server:UnshelveServer + server_backup_create = openstackclient.compute.v2.server_backup:CreateServerBackup + server_group_create = openstackclient.compute.v2.server_group:CreateServerGroup server_group_delete = openstackclient.compute.v2.server_group:DeleteServerGroup server_group_list = openstackclient.compute.v2.server_group:ListServerGroup @@ -322,6 +324,7 @@ openstack.image.v2 = image_save = openstackclient.image.v2.image:SaveImage image_show = openstackclient.image.v2.image:ShowImage image_set = openstackclient.image.v2.image:SetImage + image_unset = openstackclient.image.v2.image:UnsetImage openstack.network.v2 = address_scope_create = openstackclient.network.v2.address_scope:CreateAddressScope @@ -330,6 +333,9 @@ openstack.network.v2 = address_scope_set = openstackclient.network.v2.address_scope:SetAddressScope address_scope_show = openstackclient.network.v2.address_scope:ShowAddressScope + 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 @@ -341,6 +347,9 @@ openstack.network.v2 = network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork + network_segment_list = openstackclient.network.v2.network_segment:ListNetworkSegment + network_segment_show = openstackclient.network.v2.network_segment:ShowNetworkSegment + port_create = openstackclient.network.v2.port:CreatePort port_delete = openstackclient.network.v2.port:DeletePort port_list = openstackclient.network.v2.port:ListPort @@ -438,6 +447,8 @@ openstack.volume.v1 = volume_service_list = openstackclient.volume.v1.service:ListService + volume_transfer_request_list = openstackclient.volume.v1.volume_transfer_request:ListTransferRequests + openstack.volume.v2 = backup_create = openstackclient.volume.v2.backup:CreateBackup backup_delete = openstackclient.volume.v2.backup:DeleteBackup @@ -477,6 +488,8 @@ openstack.volume.v2 = volume_service_list = openstackclient.volume.v2.service:ListService + volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequests + [build_sphinx] source-dir = doc/source build-dir = doc/build diff --git a/test-requirements.txt b/test-requirements.txt index ffc8326..a8d6aa4 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,14 +5,14 @@ hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 discover # BSD -fixtures<2.0,>=1.3.1 # Apache-2.0/BSD -mock>=1.2 # 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 -os-testr>=0.4.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 |
