diff options
Diffstat (limited to 'openstackclient')
31 files changed, 2424 insertions, 443 deletions
diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index c30058c8..77798f90 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -155,9 +155,8 @@ class NonNegativeAction(argparse.Action): """ def __call__(self, parser, namespace, values, option_string=None): - try: - assert(int(values) >= 0) + if int(values) >= 0: setattr(namespace, self.dest, values) - except Exception: + else: msg = "%s expected a non-negative integer" % (str(option_string)) raise argparse.ArgumentTypeError(msg) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index b3d4c3b6..b497a44d 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -145,7 +145,8 @@ class ShowQuota(command.ShowOne): parser.add_argument( 'project', metavar='<project/class>', - help='Show this project or class (name/ID)', + nargs='?', + help='Show quotas for this project or class (name or ID)', ) type_group = parser.add_mutually_exclusive_group() type_group.add_argument( @@ -164,12 +165,22 @@ class ShowQuota(command.ShowOne): ) return parser + def _get_project(self, parsed_args): + if parsed_args.project is not None: + identity_client = self.app.client_manager.identity + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ).id + elif self.app.client_manager.auth_ref: + # Get the project from the current auth + project = self.app.client_manager.auth_ref.project_id + else: + project = None + return project + def get_compute_volume_quota(self, client, parsed_args): - identity_client = self.app.client_manager.identity - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ).id + project = self._get_project(parsed_args) try: if parsed_args.quota_class: @@ -189,11 +200,7 @@ class ShowQuota(command.ShowOne): if parsed_args.quota_class or parsed_args.default: return {} if self.app.client_manager.is_network_endpoint_enabled(): - identity_client = self.app.client_manager.identity - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ).id + project = self._get_project(parsed_args) return self.app.client_manager.network.get_quota(project) else: return {} diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 29e0e9d4..04674614 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -239,7 +239,8 @@ class SetFlavor(command.Command): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - flavor = compute_client.flavors.find(name=parsed_args.flavor) + flavor = utils.find_resource(compute_client.flavors, + parsed_args.flavor) flavor.set_keys(parsed_args.property) @@ -289,5 +290,6 @@ class UnsetFlavor(command.Command): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - flavor = compute_client.flavors.find(name=parsed_args.flavor) + flavor = utils.find_resource(compute_client.flavors, + parsed_args.flavor) flavor.unset_keys(parsed_args.property) diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py new file mode 100644 index 00000000..eb5745f5 --- /dev/null +++ b/openstackclient/compute/v2/server_group.py @@ -0,0 +1,182 @@ +# Copyright 2016 Huawei, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Compute v2 Server Group action implementations""" + +from openstackclient.common import command +from openstackclient.common import exceptions +from openstackclient.common import utils + + +_formatters = { + 'policies': utils.format_list, + 'members': utils.format_list, +} + + +def _get_columns(info): + columns = list(info.keys()) + if 'metadata' in columns: + # NOTE(RuiChen): The metadata of server group is always empty since API + # compatible, so hide it in order to avoid confusion. + columns.remove('metadata') + return tuple(sorted(columns)) + + +class CreateServerGroup(command.ShowOne): + """Create a new server group.""" + + def get_parser(self, prog_name): + parser = super(CreateServerGroup, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='<name>', + help='New server group name', + ) + parser.add_argument( + '--policy', + metavar='<policy>', + action='append', + required=True, + help='Add a policy to <name> ' + '(repeat option to add multiple policies)', + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + info = {} + server_group = compute_client.server_groups.create( + name=parsed_args.name, + policies=parsed_args.policy) + info.update(server_group._info) + + columns = _get_columns(info) + data = utils.get_dict_properties(info, columns, + formatters=_formatters) + return columns, data + + +class DeleteServerGroup(command.Command): + """Delete an existing server group.""" + + def get_parser(self, prog_name): + parser = super(DeleteServerGroup, self).get_parser(prog_name) + parser.add_argument( + 'server_group', + metavar='<server-group>', + nargs='+', + help='server group(s) to delete (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + result = 0 + for group in parsed_args.server_group: + try: + group_obj = utils.find_resource(compute_client.server_groups, + group) + compute_client.server_groups.delete(group_obj.id) + # Catch all exceptions in order to avoid to block the next deleting + except Exception as e: + result += 1 + self.app.log.error(e) + + if result > 0: + total = len(parsed_args.server_group) + msg = "%s of %s server groups failed to delete." % (result, total) + raise exceptions.CommandError(msg) + + +class ListServerGroup(command.Lister): + """List all server groups.""" + + def get_parser(self, prog_name): + parser = super(ListServerGroup, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Display information from all projects (admin only)', + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + 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 = ( + 'ID', + 'Name', + 'Policies', + 'Members', + 'Project Id', + 'User Id', + ) + else: + column_headers = columns = ( + 'ID', + 'Name', + 'Policies', + ) + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={ + 'Policies': utils.format_list, + 'Members': utils.format_list, + } + ) for s in data)) + + +class ShowServerGroup(command.ShowOne): + """Display server group details.""" + + def get_parser(self, prog_name): + parser = super(ShowServerGroup, self).get_parser(prog_name) + parser.add_argument( + 'server_group', + metavar='<server-group>', + help='server group to display (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + group = utils.find_resource(compute_client.server_groups, + parsed_args.server_group) + info = {} + info.update(group._info) + columns = _get_columns(info) + data = utils.get_dict_properties(info, columns, + formatters=_formatters) + return columns, data diff --git a/openstackclient/locale/de/LC_MESSAGES/openstackclient.po b/openstackclient/locale/de/LC_MESSAGES/openstackclient.po index 1c94b9d4..f7445b1c 100644 --- a/openstackclient/locale/de/LC_MESSAGES/openstackclient.po +++ b/openstackclient/locale/de/LC_MESSAGES/openstackclient.po @@ -5,13 +5,12 @@ # # Translators: # Ettore Atalan <atalanttore@googlemail.com>, 2014-2015 -# Andreas Jaeger <jaegerandi@gmail.com>, 2015. #zanata -# OpenStack Infra <zanata@openstack.org>, 2015. #zanata +# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 2.0.1.dev168\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-19 02:20+0000\n" +"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 05:02+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -167,10 +166,6 @@ msgid "Endpoint ID to display" msgstr "Anzuzeigende Endpunktkennung" #, python-format -msgid "Error creating server snapshot: %s" -msgstr "Fehler beim Erstellen der Server-Schattenkopie: %s" - -#, python-format msgid "Error creating server: %s" msgstr "Fehler beim Erstellen des Servers: %s" @@ -181,11 +176,6 @@ msgstr "Fehler beim Löschen des Servers: %s" msgid "Error retrieving diagnostics data" msgstr "Fehler beim Abrufen der Diagnosedaten" -msgid "File to inject into image before boot (repeat for multiple files)" -msgstr "" -"Vor dem Start auf diesem Abbild einzufügende Datei (für mehrere Dateien " -"wiederholen)" - msgid "Filter by parent region ID" msgstr "Nach übergeordneter Regionskennung filtern" @@ -338,11 +328,6 @@ msgstr "Zu löschende(s) Projekt(e) (Name oder Kennung)" msgid "Prompt interactively for password" msgstr "Interaktiv nach dem Passwort abfragen" -msgid "Property key to remove from server (repeat to unset multiple values)" -msgstr "" -"Vom Server zu entfernender Eigenschaftsschlüssel (zum Aufheben von mehreren " -"Werten wiederholen)" - msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" @@ -446,11 +431,6 @@ msgstr "" "Legen Sie eine Projekteigenschaft fest (wiederholen Sie die Option, um " "mehrere Eigenschaften festzulegen)" -msgid "Set a property on this server (repeat for multiple values)" -msgstr "" -"Legen Sie eine Eigenschaft auf diesem Server fest (für mehrere Werte " -"wiederholen)" - 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 " diff --git a/openstackclient/locale/openstackclient.pot b/openstackclient/locale/openstackclient.pot index afc89266..e197fa7b 100644 --- a/openstackclient/locale/openstackclient.pot +++ b/openstackclient/locale/openstackclient.pot @@ -7,9 +7,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 2.0.1.dev168\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-19 06:03+0000\n" +"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" @@ -18,123 +18,142 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.2.0\n" -#: openstackclient/api/auth.py:144 +#: openstackclient/api/auth.py:148 msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" msgstr "" -#: openstackclient/api/auth.py:147 +#: 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:155 +#: 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:161 openstackclient/api/auth.py:167 +#: 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:163 +#: 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:169 +#: openstackclient/api/auth.py:174 msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" msgstr "" -#: openstackclient/compute/v2/availability_zone.py:72 -#: openstackclient/compute/v2/server.py:737 -#: openstackclient/identity/v2_0/endpoint.py:114 -#: openstackclient/identity/v2_0/project.py:145 -#: openstackclient/identity/v2_0/service.py:128 -#: openstackclient/identity/v2_0/user.py:174 +#: 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/compute/v2/server.py:183 -#: openstackclient/compute/v2/server.py:219 -#: openstackclient/compute/v2/server.py:569 -#: openstackclient/compute/v2/server.py:923 -#: openstackclient/compute/v2/server.py:1031 -#: openstackclient/compute/v2/server.py:1086 -#: openstackclient/compute/v2/server.py:1179 -#: openstackclient/compute/v2/server.py:1219 -#: openstackclient/compute/v2/server.py:1245 -#: openstackclient/compute/v2/server.py:1336 -#: openstackclient/compute/v2/server.py:1420 -#: openstackclient/compute/v2/server.py:1457 -#: openstackclient/compute/v2/server.py:1732 -#: openstackclient/compute/v2/server.py:1756 +#: 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:188 +#: openstackclient/compute/v2/server.py:200 msgid "Security group to add (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:224 +#: openstackclient/compute/v2/server.py:232 msgid "Volume to add (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:229 +#: openstackclient/compute/v2/server.py:237 msgid "Server internal device name for volume" msgstr "" -#: openstackclient/compute/v2/server.py:265 -#: openstackclient/compute/v2/server.py:1341 +#: openstackclient/compute/v2/server.py:269 +#: openstackclient/compute/v2/server.py:1348 msgid "New server name" msgstr "" -#: openstackclient/compute/v2/server.py:273 +#: openstackclient/compute/v2/server.py:277 msgid "Create server from this image (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:278 +#: openstackclient/compute/v2/server.py:282 msgid "Create server from this volume (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:284 +#: openstackclient/compute/v2/server.py:288 msgid "Create server with this flavor (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:291 +#: openstackclient/compute/v2/server.py:295 msgid "" -"Security group to assign to this server (name or ID) (repeat for multiple" -" groups)" +"Security group to assign to this server (name or ID) (repeat option to " +"set multiple groups)" msgstr "" -#: openstackclient/compute/v2/server.py:297 +#: openstackclient/compute/v2/server.py:301 msgid "Keypair to inject into this server (optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:303 -msgid "Set a property on this server (repeat for multiple values)" +#: 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:311 -msgid "File to inject into image before boot (repeat for multiple files)" +#: 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:317 +#: openstackclient/compute/v2/server.py:321 msgid "User data file to serve from the metadata server" msgstr "" -#: openstackclient/compute/v2/server.py:322 +#: openstackclient/compute/v2/server.py:326 msgid "Select an availability zone for the server" msgstr "" -#: openstackclient/compute/v2/server.py:329 +#: 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:339 +#: 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. " @@ -143,296 +162,304 @@ msgid "" "-fixed-ip: IPv6 fixed address for NIC (optional)." msgstr "" -#: openstackclient/compute/v2/server.py:352 +#: openstackclient/compute/v2/server.py:356 msgid "Hints for the scheduler (optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:358 +#: 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:366 +#: openstackclient/compute/v2/server.py:370 msgid "Minimum number of servers to launch (default=1)" msgstr "" -#: openstackclient/compute/v2/server.py:373 +#: openstackclient/compute/v2/server.py:377 msgid "Maximum number of servers to launch (default=1)" msgstr "" -#: openstackclient/compute/v2/server.py:378 +#: openstackclient/compute/v2/server.py:382 msgid "Wait for build to complete" msgstr "" -#: openstackclient/compute/v2/server.py:418 +#: openstackclient/compute/v2/server.py:421 msgid "min instances should be <= max instances" msgstr "" -#: openstackclient/compute/v2/server.py:421 +#: openstackclient/compute/v2/server.py:424 msgid "min instances should be > 0" msgstr "" -#: openstackclient/compute/v2/server.py:424 +#: openstackclient/compute/v2/server.py:427 msgid "max instances should be > 0" msgstr "" -#: openstackclient/compute/v2/server.py:453 +#: 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:465 +#: 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:485 +#: 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:550 +#: openstackclient/compute/v2/server.py:553 #, python-format msgid "Error creating server: %s" msgstr "" -#: openstackclient/compute/v2/server.py:552 +#: openstackclient/compute/v2/server.py:555 msgid "" "\n" "Error creating server" msgstr "" -#: openstackclient/compute/v2/server.py:574 +#: 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:579 +#: openstackclient/compute/v2/server.py:608 msgid "Wait for image create to complete" msgstr "" -#: openstackclient/compute/v2/server.py:609 +#: openstackclient/compute/v2/server.py:637 #, python-format -msgid "Error creating server snapshot: %s" +msgid "Error creating snapshot of server: %s" msgstr "" -#: openstackclient/compute/v2/server.py:611 +#: openstackclient/compute/v2/server.py:639 msgid "" "\n" "Error creating server snapshot" msgstr "" -#: openstackclient/compute/v2/server.py:633 +#: openstackclient/compute/v2/server.py:656 msgid "Server(s) to delete (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:638 +#: openstackclient/compute/v2/server.py:661 msgid "Wait for delete to complete" msgstr "" -#: openstackclient/compute/v2/server.py:657 +#: openstackclient/compute/v2/server.py:679 #, python-format msgid "Error deleting server: %s" msgstr "" -#: openstackclient/compute/v2/server.py:659 +#: openstackclient/compute/v2/server.py:681 msgid "" "\n" "Error deleting server" msgstr "" -#: openstackclient/compute/v2/server.py:673 +#: openstackclient/compute/v2/server.py:693 msgid "Only return instances that match the reservation" msgstr "" -#: openstackclient/compute/v2/server.py:678 +#: openstackclient/compute/v2/server.py:698 msgid "Regular expression to match IP addresses" msgstr "" -#: openstackclient/compute/v2/server.py:683 +#: openstackclient/compute/v2/server.py:703 msgid "Regular expression to match IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:688 +#: openstackclient/compute/v2/server.py:708 msgid "Regular expression to match names" msgstr "" -#: openstackclient/compute/v2/server.py:693 +#: openstackclient/compute/v2/server.py:713 msgid "Regular expression to match instance name (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:699 +#: openstackclient/compute/v2/server.py:719 msgid "Search by server status" msgstr "" -#: openstackclient/compute/v2/server.py:704 +#: openstackclient/compute/v2/server.py:724 msgid "Search by flavor (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:709 +#: openstackclient/compute/v2/server.py:729 msgid "Search by image (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:714 +#: openstackclient/compute/v2/server.py:734 msgid "Search by hostname" msgstr "" -#: openstackclient/compute/v2/server.py:720 +#: openstackclient/compute/v2/server.py:740 msgid "Include all projects (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:730 +#: openstackclient/compute/v2/server.py:750 msgid "Search by user (admin only) (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:888 +#: openstackclient/compute/v2/server.py:905 msgid "Server(s) to lock (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:928 +#: openstackclient/compute/v2/server.py:942 msgid "Target hostname" msgstr "" -#: openstackclient/compute/v2/server.py:936 +#: openstackclient/compute/v2/server.py:950 msgid "Perform a shared live migration (default)" msgstr "" -#: openstackclient/compute/v2/server.py:942 +#: openstackclient/compute/v2/server.py:956 msgid "Perform a block live migration" msgstr "" -#: openstackclient/compute/v2/server.py:949 +#: openstackclient/compute/v2/server.py:963 msgid "Allow disk over-commit on the destination host" msgstr "" -#: openstackclient/compute/v2/server.py:956 +#: openstackclient/compute/v2/server.py:970 msgid "Do not over-commit disk on the destination host (default)" msgstr "" -#: openstackclient/compute/v2/server.py:962 -#: openstackclient/compute/v2/server.py:1265 +#: openstackclient/compute/v2/server.py:976 +#: openstackclient/compute/v2/server.py:1256 msgid "Wait for resize to complete" msgstr "" -#: openstackclient/compute/v2/server.py:990 -#: openstackclient/compute/v2/server.py:1290 +#: openstackclient/compute/v2/server.py:1003 +#: openstackclient/compute/v2/server.py:1280 msgid "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:992 +#: openstackclient/compute/v2/server.py:1005 msgid "" "\n" "Error migrating server" msgstr "" -#: openstackclient/compute/v2/server.py:1007 +#: openstackclient/compute/v2/server.py:1018 msgid "Server(s) to pause (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1040 +#: openstackclient/compute/v2/server.py:1048 msgid "Perform a hard reboot" msgstr "" -#: openstackclient/compute/v2/server.py:1048 +#: openstackclient/compute/v2/server.py:1056 msgid "Perform a soft reboot" msgstr "" -#: openstackclient/compute/v2/server.py:1053 +#: openstackclient/compute/v2/server.py:1061 msgid "Wait for reboot to complete" msgstr "" -#: openstackclient/compute/v2/server.py:1070 +#: openstackclient/compute/v2/server.py:1077 msgid "" "\n" "Reboot complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:1072 +#: openstackclient/compute/v2/server.py:1079 msgid "" "\n" "Error rebooting server\n" msgstr "" -#: openstackclient/compute/v2/server.py:1091 +#: 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:1102 +#: openstackclient/compute/v2/server.py:1107 msgid "Wait for rebuild to complete" msgstr "" -#: openstackclient/compute/v2/server.py:1124 +#: openstackclient/compute/v2/server.py:1128 msgid "" "\n" "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:1126 +#: openstackclient/compute/v2/server.py:1130 msgid "" "\n" "Error rebuilding server" msgstr "" -#: openstackclient/compute/v2/server.py:1143 +#: openstackclient/compute/v2/server.py:1145 msgid "Name or ID of server to use" msgstr "" -#: openstackclient/compute/v2/server.py:1148 +#: openstackclient/compute/v2/server.py:1150 msgid "Name or ID of security group to remove from server" msgstr "" -#: openstackclient/compute/v2/server.py:1184 +#: openstackclient/compute/v2/server.py:1182 msgid "Volume to remove (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1250 +#: openstackclient/compute/v2/server.py:1241 msgid "Resize server to specified flavor" msgstr "" -#: openstackclient/compute/v2/server.py:1255 +#: openstackclient/compute/v2/server.py:1246 msgid "Confirm server resize is complete" msgstr "" -#: openstackclient/compute/v2/server.py:1260 +#: openstackclient/compute/v2/server.py:1251 msgid "Restore server state before resize" msgstr "" -#: openstackclient/compute/v2/server.py:1292 +#: openstackclient/compute/v2/server.py:1282 msgid "" "\n" "Error resizing server" msgstr "" -#: openstackclient/compute/v2/server.py:1311 +#: 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:1346 +#: openstackclient/compute/v2/server.py:1353 msgid "Set new root password (interactive only)" msgstr "" -#: openstackclient/compute/v2/server.py:1352 +#: 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:1376 +#: openstackclient/compute/v2/server.py:1382 msgid "New password: " msgstr "" -#: openstackclient/compute/v2/server.py:1377 +#: openstackclient/compute/v2/server.py:1383 msgid "Retype new password: " msgstr "" -#: openstackclient/compute/v2/server.py:1381 +#: openstackclient/compute/v2/server.py:1387 msgid "Passwords do not match, password unchanged" msgstr "" -#: openstackclient/compute/v2/server.py:1396 +#: openstackclient/compute/v2/server.py:1400 msgid "Server(s) to shelve (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1426 +#: openstackclient/compute/v2/server.py:1427 msgid "Display server diagnostics information" msgstr "" @@ -440,390 +467,944 @@ msgstr "" msgid "Error retrieving diagnostics data" msgstr "" -#: openstackclient/compute/v2/server.py:1462 +#: openstackclient/compute/v2/server.py:1460 msgid "Login name (ssh -l option)" msgstr "" -#: openstackclient/compute/v2/server.py:1474 +#: openstackclient/compute/v2/server.py:1472 msgid "Destination port (ssh -p option)" msgstr "" -#: openstackclient/compute/v2/server.py:1486 +#: openstackclient/compute/v2/server.py:1484 msgid "Private key file (ssh -i option)" msgstr "" -#: openstackclient/compute/v2/server.py:1497 +#: openstackclient/compute/v2/server.py:1495 msgid "Options in ssh_config(5) format (ssh -o option)" msgstr "" -#: openstackclient/compute/v2/server.py:1511 +#: openstackclient/compute/v2/server.py:1509 msgid "Use only IPv4 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1518 +#: openstackclient/compute/v2/server.py:1516 msgid "Use only IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1527 +#: openstackclient/compute/v2/server.py:1525 msgid "Use public IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1535 +#: openstackclient/compute/v2/server.py:1533 msgid "Use private IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1542 +#: openstackclient/compute/v2/server.py:1540 msgid "Use other IP address (public, private, etc)" msgstr "" -#: openstackclient/compute/v2/server.py:1605 +#: openstackclient/compute/v2/server.py:1600 msgid "Server(s) to start (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1630 +#: openstackclient/compute/v2/server.py:1622 msgid "Server(s) to stop (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1655 +#: openstackclient/compute/v2/server.py:1644 msgid "Server(s) to suspend (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1681 +#: openstackclient/compute/v2/server.py:1667 msgid "Server(s) to unlock (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1707 +#: openstackclient/compute/v2/server.py:1690 msgid "Server(s) to unpause (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1763 -msgid "Property key to remove from server (repeat to unset multiple values)" +#: 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:1794 +#: openstackclient/compute/v2/server.py:1768 msgid "Server(s) to unshelve (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/catalog.py:75 -#: openstackclient/identity/v3/catalog.py:72 +#: 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:40 -#: openstackclient/identity/v3/ec2creds.py:65 +#: 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:48 -#: openstackclient/identity/v3/ec2creds.py:73 +#: 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:99 -#: openstackclient/identity/v2_0/ec2creds.py:172 -#: openstackclient/identity/v3/ec2creds.py:129 -#: openstackclient/identity/v3/ec2creds.py:187 +#: 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:104 -#: openstackclient/identity/v3/ec2creds.py:134 +#: 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:134 -#: openstackclient/identity/v3/ec2creds.py:156 +#: 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:177 -#: openstackclient/identity/v3/ec2creds.py:192 +#: 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:40 +#: openstackclient/identity/v2_0/endpoint.py:34 msgid "New endpoint service (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/endpoint.py:46 +#: openstackclient/identity/v2_0/endpoint.py:40 msgid "New endpoint public URL (required)" msgstr "" -#: openstackclient/identity/v2_0/endpoint.py:51 +#: openstackclient/identity/v2_0/endpoint.py:45 msgid "New endpoint admin URL" msgstr "" -#: openstackclient/identity/v2_0/endpoint.py:56 +#: openstackclient/identity/v2_0/endpoint.py:50 msgid "New endpoint internal URL" msgstr "" -#: openstackclient/identity/v2_0/endpoint.py:61 +#: openstackclient/identity/v2_0/endpoint.py:55 msgid "New endpoint region ID" msgstr "" -#: openstackclient/identity/v2_0/endpoint.py:93 +#: openstackclient/identity/v2_0/endpoint.py:84 msgid "Endpoint ID to delete" msgstr "" -#: openstackclient/identity/v2_0/endpoint.py:149 +#: openstackclient/identity/v2_0/endpoint.py:133 msgid "Endpoint ID to display" msgstr "" -#: openstackclient/identity/v2_0/project.py:41 +#: openstackclient/identity/v2_0/project.py:36 msgid "New project name" msgstr "" -#: openstackclient/identity/v2_0/project.py:46 +#: openstackclient/identity/v2_0/project.py:41 msgid "Project description" msgstr "" -#: openstackclient/identity/v2_0/project.py:52 +#: openstackclient/identity/v2_0/project.py:47 msgid "Enable project (default)" msgstr "" -#: openstackclient/identity/v2_0/project.py:57 -#: openstackclient/identity/v2_0/project.py:194 +#: openstackclient/identity/v2_0/project.py:52 +#: openstackclient/identity/v2_0/project.py:179 msgid "Disable project" msgstr "" -#: openstackclient/identity/v2_0/project.py:63 +#: 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:69 -#: openstackclient/identity/v3/project.py:80 +#: openstackclient/identity/v2_0/project.py:64 +#: openstackclient/identity/v3/project.py:75 msgid "Return existing project" msgstr "" -#: openstackclient/identity/v2_0/project.py:117 +#: openstackclient/identity/v2_0/project.py:109 msgid "Project(s) to delete (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/project.py:173 -#: openstackclient/identity/v2_0/project.py:313 +#: 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:178 +#: openstackclient/identity/v2_0/project.py:163 msgid "Set project name" msgstr "" -#: openstackclient/identity/v2_0/project.py:183 +#: openstackclient/identity/v2_0/project.py:168 msgid "Set project description" msgstr "" -#: openstackclient/identity/v2_0/project.py:189 +#: openstackclient/identity/v2_0/project.py:174 msgid "Enable project" msgstr "" -#: openstackclient/identity/v2_0/project.py:200 +#: 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:253 +#: openstackclient/identity/v2_0/project.py:234 msgid "Project to display (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/project.py:320 +#: 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:41 +#: openstackclient/identity/v2_0/role.py:36 msgid "Role to add to <project>:<user> (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/role.py:47 -#: openstackclient/identity/v2_0/role.py:309 +#: 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:53 -#: openstackclient/identity/v2_0/role.py:315 +#: 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:87 +#: openstackclient/identity/v2_0/role.py:79 msgid "New role name" msgstr "" -#: openstackclient/identity/v2_0/role.py:92 -#: openstackclient/identity/v3/role.py:165 +#: openstackclient/identity/v2_0/role.py:84 +#: openstackclient/identity/v3/role.py:155 msgid "Return existing role" msgstr "" -#: openstackclient/identity/v2_0/role.py:127 +#: openstackclient/identity/v2_0/role.py:116 msgid "Role(s) to delete (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/role.py:194 -#: openstackclient/identity/v2_0/role.py:257 +#: 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:208 -#: openstackclient/identity/v2_0/role.py:263 +#: 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:236 +#: openstackclient/identity/v2_0/role.py:218 msgid "User to list (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/role.py:241 +#: openstackclient/identity/v2_0/role.py:223 msgid "Filter users by <project> (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/role.py:303 +#: openstackclient/identity/v2_0/role.py:282 msgid "Role to remove (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/role.py:344 +#: openstackclient/identity/v2_0/role.py:320 msgid "Role to display (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/service.py:42 +#: openstackclient/identity/v2_0/service.py:36 msgid "New service type (compute, image, identity, volume, etc)" msgstr "" -#: openstackclient/identity/v2_0/service.py:53 +#: openstackclient/identity/v2_0/service.py:47 msgid "New service name" msgstr "" -#: openstackclient/identity/v2_0/service.py:58 +#: openstackclient/identity/v2_0/service.py:52 msgid "New service description" msgstr "" -#: openstackclient/identity/v2_0/service.py:78 +#: 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:105 +#: openstackclient/identity/v2_0/service.py:96 msgid "Service to delete (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/service.py:156 +#: openstackclient/identity/v2_0/service.py:140 msgid "Service to display (type, name or ID)" msgstr "" -#: openstackclient/identity/v2_0/service.py:162 +#: openstackclient/identity/v2_0/service.py:146 msgid "Show service catalog information" msgstr "" -#: openstackclient/identity/v2_0/service.py:180 +#: 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:55 +#: openstackclient/identity/v2_0/token.py:50 msgid "Token to be deleted" msgstr "" -#: openstackclient/identity/v2_0/user.py:40 +#: openstackclient/identity/v2_0/user.py:35 msgid "New user name" msgstr "" -#: openstackclient/identity/v2_0/user.py:45 +#: openstackclient/identity/v2_0/user.py:40 msgid "Default project (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/user.py:50 -#: openstackclient/identity/v2_0/user.py:273 +#: 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:56 -#: openstackclient/identity/v2_0/user.py:279 +#: 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:61 -#: openstackclient/identity/v2_0/user.py:284 +#: 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:67 -#: openstackclient/identity/v2_0/user.py:290 +#: 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:72 -#: openstackclient/identity/v2_0/user.py:295 +#: openstackclient/identity/v2_0/user.py:67 +#: openstackclient/identity/v2_0/user.py:280 msgid "Disable user" msgstr "" -#: openstackclient/identity/v2_0/user.py:77 -#: openstackclient/identity/v3/user.py:90 +#: openstackclient/identity/v2_0/user.py:72 +#: openstackclient/identity/v3/user.py:86 msgid "Return existing user" msgstr "" -#: openstackclient/identity/v2_0/user.py:141 +#: openstackclient/identity/v2_0/user.py:133 msgid "User(s) to delete (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/user.py:168 +#: openstackclient/identity/v2_0/user.py:156 msgid "Filter users by project (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/user.py:258 +#: openstackclient/identity/v2_0/user.py:243 msgid "User to change (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/user.py:263 +#: openstackclient/identity/v2_0/user.py:248 msgid "Set user name" msgstr "" -#: openstackclient/identity/v2_0/user.py:268 +#: openstackclient/identity/v2_0/user.py:253 msgid "Set default project (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/user.py:361 +#: openstackclient/identity/v2_0/user.py:342 msgid "User to display (name or ID)" msgstr "" -#: openstackclient/identity/v3/domain.py:62 +#: openstackclient/identity/v3/domain.py:57 msgid "Return existing domain" msgstr "" -#: openstackclient/identity/v3/group.py:141 +#: openstackclient/identity/v3/group.py:130 msgid "Return existing group" msgstr "" -#: openstackclient/identity/v3/region.py:39 +#: openstackclient/identity/v3/region.py:33 msgid "New region ID" msgstr "" -#: openstackclient/identity/v3/region.py:44 +#: openstackclient/identity/v3/region.py:38 msgid "Parent region ID" msgstr "" -#: openstackclient/identity/v3/region.py:49 -#: openstackclient/identity/v3/region.py:144 +#: openstackclient/identity/v3/region.py:43 +#: openstackclient/identity/v3/region.py:128 msgid "New region description" msgstr "" -#: openstackclient/identity/v3/region.py:79 +#: openstackclient/identity/v3/region.py:70 msgid "Region ID to delete" msgstr "" -#: openstackclient/identity/v3/region.py:101 +#: openstackclient/identity/v3/region.py:88 msgid "Filter by parent region ID" msgstr "" -#: openstackclient/identity/v3/region.py:134 +#: openstackclient/identity/v3/region.py:118 msgid "Region to modify" msgstr "" -#: openstackclient/identity/v3/region.py:139 +#: openstackclient/identity/v3/region.py:123 msgid "New parent region ID" msgstr "" -#: openstackclient/identity/v3/region.py:175 +#: 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 26032666..a121acab 100644 --- a/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po +++ b/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po @@ -4,12 +4,12 @@ # python-openstackclient project. # # Translators: -# OpenStack Infra <zanata@openstack.org>, 2015. #zanata +# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 2.0.1.dev168\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-19 02:20+0000\n" +"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 05:02+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -126,19 +126,12 @@ msgid "Endpoint ID to display" msgstr "要顯示的端點識別號" #, python-format -msgid "Error creating server snapshot: %s" -msgstr "新增雲實例即時存檔時出錯:%s" - -#, python-format msgid "Error creating server: %s" msgstr "新增雲實例時出錯:%s" msgid "Error retrieving diagnostics data" msgstr "獲得診斷資料時出錯" -msgid "File to inject into image before boot (repeat for multiple files)" -msgstr "在開機前要注入映像檔的檔案(為多個檔案重復指定)" - msgid "Filter by parent region ID" msgstr "以父地區識別號來篩選" @@ -287,9 +280,6 @@ msgstr "要刪除的專案(名稱或識別號)" msgid "Prompt interactively for password" msgstr "為密碼互動提示" -msgid "Property key to remove from server (repeat to unset multiple values)" -msgstr "要從雲實例上移除的屬性鍵(重復來取消選擇多個值)" - msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" @@ -385,9 +375,6 @@ msgstr "要顯示的伺服器(類型、名稱或識別號)" msgid "Set a project property (repeat option to set multiple properties)" msgstr "設定專案屬性(重復這選項來設定多個屬性)" -msgid "Set a property on this server (repeat for multiple values)" -msgstr "為此伺服器設定屬性(為多個值重複設定)" - msgid "Set default project (name or ID)" msgstr "設定預設專案(名稱或識別號)" diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index dca9efc4..be06d2b5 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -14,6 +14,7 @@ import logging from openstack import connection +from openstack import profile from openstackclient.common import utils @@ -31,8 +32,13 @@ API_VERSIONS = { def make_client(instance): """Returns a network proxy""" + prof = profile.Profile() + prof.set_region(API_NAME, instance._region_name) + prof.set_version(API_NAME, instance._api_version[API_NAME]) conn = connection.Connection(authenticator=instance.session.auth, - verify=instance.session.verify) + verify=instance.session.verify, + cert=instance.session.cert, + profile=prof) LOG.debug('Connection: %s', conn) LOG.debug('Network client initialized using OpenStack SDK: %s', conn.network) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index d57a1ed6..4b77971a 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -86,10 +86,40 @@ def _get_attrs(client_manager, parsed_args): attrs['is_default'] = False if parsed_args.default: attrs['is_default'] = True - + # Update Provider network options + if parsed_args.provider_network_type: + attrs['provider:network_type'] = parsed_args.provider_network_type + if parsed_args.physical_network: + attrs['provider:physical_network'] = parsed_args.physical_network + if parsed_args.segmentation_id: + attrs['provider:segmentation_id'] = parsed_args.segmentation_id return attrs +def _add_provider_network_options(parser): + # Add provider network options + parser.add_argument( + '--provider-network-type', + metavar='<provider-network-type>', + choices=['flat', 'gre', 'local', + 'vlan', 'vxlan'], + help=_("The physical mechanism by which the virtual network " + "is implemented. The supported options are: " + "flat, gre, local, vlan, vxlan")) + parser.add_argument( + '--provider-physical-network', + metavar='<provider-physical-network>', + dest='physical_network', + help=_("Name of the physical network over which the virtual " + "network is implemented")) + parser.add_argument( + '--provider-segment', + metavar='<provider-segment>', + dest='segmentation_id', + help=_("VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN " + "networks")) + + def _get_attrs_compute(client_manager, parsed_args): attrs = {} if parsed_args.name is not None: @@ -100,7 +130,6 @@ def _get_attrs_compute(client_manager, parsed_args): attrs['share_address'] = False if parsed_args.subnet is not None: attrs['cidr'] = parsed_args.subnet - return attrs @@ -180,29 +209,7 @@ class CreateNetwork(common.NetworkAndComputeShowOne): help=_("Do not use the network as the default external network. " "(default)") ) - parser.add_argument( - '--provider-network-type', - metavar='<provider-network-type>', - choices=['flat', 'gre', 'local', - 'vlan', 'vxlan'], - help=_("The physical mechanism by which the virtual network " - "is implemented. The supported options are: " - "flat, gre, local, vlan, vxlan") - ) - parser.add_argument( - '--provider-physical-network', - metavar='<provider-physical-network>', - dest='physical_network', - help=_("Name of the physical network over which the virtual " - "network is implemented") - ) - parser.add_argument( - '--provider-segment', - metavar='<provider-segment>', - dest='segmentation_id', - help=_("VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN " - "networks") - ) + _add_provider_network_options(parser) return parser def update_parser_compute(self, parser): @@ -215,12 +222,6 @@ class CreateNetwork(common.NetworkAndComputeShowOne): def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) - if parsed_args.provider_network_type: - attrs['provider:network_type'] = parsed_args.provider_network_type - if parsed_args.physical_network: - attrs['provider:physical_network'] = parsed_args.physical_network - if parsed_args.segmentation_id: - attrs['provider:segmentation_id'] = parsed_args.segmentation_id obj = client.create_network(**attrs) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) @@ -412,6 +413,7 @@ class SetNetwork(command.Command): action='store_true', help=_("Do not use the network as the default external network") ) + _add_provider_network_options(parser) return parser def take_action(self, parsed_args): diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index fbfce4d3..9b6161fd 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -249,7 +249,8 @@ class CreatePort(command.ShowOne): parser.add_argument( 'name', metavar='<name>', - help='Name of this port') + help=_("Name of this port") + ) # TODO(singhj): Add support for extended options: # qos,security groups,dhcp, address pairs return parser diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 67472de0..5b22a0dd 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -13,6 +13,7 @@ """Security Group Rule action implementations""" +import argparse import six try: @@ -242,14 +243,50 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): ) return parser + def update_parser_network(self, parser): + # Accept but hide the argument for consistency with compute. + # Network will always return all projects for an admin. + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=argparse.SUPPRESS + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_("List additional fields in output") + ) + return parser + + def update_parser_compute(self, parser): + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=_("Display information from all projects (admin only)") + ) + # Accept but hide the argument for consistency with network. + # There are no additional fields to display at this time. + parser.add_argument( + '--long', + action='store_false', + default=False, + help=argparse.SUPPRESS + ) + return parser + def _get_column_headers(self, parsed_args): column_headers = ( 'ID', 'IP Protocol', 'IP Range', 'Port Range', - 'Remote Security Group', ) + if parsed_args.long: + column_headers = column_headers + ('Direction', 'Ethertype',) + column_headers = column_headers + ('Remote Security Group',) if parsed_args.group is None: column_headers = column_headers + ('Security Group',) return column_headers @@ -261,8 +298,10 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): 'protocol', 'remote_ip_prefix', 'port_range_min', - 'remote_group_id', ) + if parsed_args.long: + columns = columns + ('direction', 'ethertype',) + columns = columns + ('remote_group_id',) # Get the security group rules using the requested query. query = {} @@ -309,7 +348,8 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): rules_to_list = group.rules else: columns = columns + ('parent_group_id',) - for group in client.security_groups.list(): + search = {'all_tenants': parsed_args.all_projects} + for group in client.security_groups.list(search_opts=search): rules_to_list.extend(group.rules) # NOTE(rtheis): Turn the raw rules into resources. diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 715e6620..fb441cbf 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -14,8 +14,6 @@ """Subnet action implementations""" import copy -from json.encoder import JSONEncoder - from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import parseractions @@ -31,10 +29,8 @@ def _format_allocation_pools(data): def _format_host_routes(data): - try: - return '\n'.join([JSONEncoder().encode(route) for route in data]) - except (TypeError, KeyError): - return '' + # Map the host route keys to match --host-route option. + return utils.format_list_of_dicts(convert_entries_to_gateway(data)) _formatters = { @@ -89,8 +85,9 @@ def convert_entries_to_nexthop(entries): # Change 'gateway' entry to 'nexthop' changed_entries = copy.deepcopy(entries) for entry in changed_entries: - entry['nexthop'] = entry['gateway'] - del entry['gateway'] + if 'gateway' in entry: + entry['nexthop'] = entry['gateway'] + del entry['gateway'] return changed_entries @@ -99,8 +96,9 @@ def convert_entries_to_gateway(entries): # Change 'nexthop' entry to 'gateway' changed_entries = copy.deepcopy(entries) for entry in changed_entries: - entry['gateway'] = entry['nexthop'] - del entry['nexthop'] + if 'nexthop' in entry: + entry['gateway'] = entry['nexthop'] + del entry['nexthop'] return changed_entries diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 482b5ecf..f1174dda 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -55,6 +55,16 @@ def _get_attrs(client_manager, parsed_args): if 'no_address_scope' in parsed_args and parsed_args.no_address_scope: attrs['address_scope_id'] = None + if parsed_args.default: + attrs['is_default'] = True + if parsed_args.no_default: + attrs['is_default'] = False + + if 'share' in parsed_args and parsed_args.share: + attrs['shared'] = True + if 'no_share' in parsed_args and parsed_args.no_share: + attrs['shared'] = False + # "subnet pool set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: identity_client = client_manager.identity @@ -68,18 +78,20 @@ def _get_attrs(client_manager, parsed_args): return attrs -def _add_prefix_options(parser): +def _add_prefix_options(parser, for_create=False): parser.add_argument( '--pool-prefix', metavar='<pool-prefix>', dest='prefixes', action='append', + required=for_create, help=_("Set subnet pool prefixes (in CIDR notation) " "(repeat option to set multiple prefixes)") ) parser.add_argument( '--default-prefix-length', metavar='<default-prefix-length>', + type=int, action=parseractions.NonNegativeAction, help=_("Set subnet pool default prefix length") ) @@ -87,16 +99,32 @@ def _add_prefix_options(parser): '--min-prefix-length', metavar='<min-prefix-length>', action=parseractions.NonNegativeAction, + type=int, help=_("Set subnet pool minimum prefix length") ) parser.add_argument( '--max-prefix-length', metavar='<max-prefix-length>', + type=int, action=parseractions.NonNegativeAction, help=_("Set subnet pool maximum prefix length") ) +def _add_default_options(parser): + default_group = parser.add_mutually_exclusive_group() + default_group.add_argument( + '--default', + action='store_true', + help=_("Set this as a default subnet pool"), + ) + default_group.add_argument( + '--no-default', + action='store_true', + help=_("Set this as a non-default subnet pool"), + ) + + class CreateSubnetPool(command.ShowOne): """Create subnet pool""" @@ -107,7 +135,7 @@ class CreateSubnetPool(command.ShowOne): metavar='<name>', help=_("Name of the new subnet pool") ) - _add_prefix_options(parser) + _add_prefix_options(parser, for_create=True) parser.add_argument( '--project', metavar='<project>', @@ -121,6 +149,18 @@ class CreateSubnetPool(command.ShowOne): "(name or ID), prefixes must be unique across address " "scopes") ) + _add_default_options(parser) + shared_group = parser.add_mutually_exclusive_group() + shared_group.add_argument( + '--share', + action='store_true', + help=_("Set this subnet pool as shared"), + ) + shared_group.add_argument( + '--no-share', + action='store_true', + help=_("Set this subnet pool as not shared"), + ) return parser def take_action(self, parsed_args): @@ -176,6 +216,8 @@ class ListSubnetPool(command.Lister): 'Prefixes', 'Default Prefix Length', 'Address Scope', + 'Default Subnet Pool', + 'Shared', ) columns = ( 'id', @@ -183,6 +225,8 @@ class ListSubnetPool(command.Lister): 'prefixes', 'default_prefixlen', 'address_scope_id', + 'is_default', + 'shared', ) else: headers = ( @@ -232,6 +276,8 @@ class SetSubnetPool(command.Command): action='store_true', help=_("Remove address scope associated with the subnet pool") ) + _add_default_options(parser) + return parser def take_action(self, parsed_args): diff --git a/openstackclient/shell.py b/openstackclient/shell.py index b7bc7b1a..b96fb089 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -16,6 +16,7 @@ """Command-line interface to the OpenStack APIs""" +import argparse import getpass import logging import sys @@ -131,6 +132,16 @@ class OpenStackShell(app.App): self.log.info("END return value: %s", ret_val) def init_profile(self): + # NOTE(dtroyer): Remove this 'if' block when the --profile global + # option is removed + if osprofiler_profiler and self.options.old_profile: + self.log.warning( + 'The --profile option is deprecated, ' + 'please use --os-profile instead' + ) + if not self.options.profile: + self.options.profile = self.options.old_profile + self.do_profile = osprofiler_profiler and self.options.profile if self.do_profile: osprofiler_profiler.init(self.options.profile) @@ -144,7 +155,7 @@ class OpenStackShell(app.App): # bigger than most big default one (CRITICAL) or something like # that (PROFILE = 60 for instance), but not sure we need it here. self.log.warning("Trace ID: %s" % trace_id) - self.log.warning("To display trace use next command:\n" + self.log.warning("Display trace with command:\n" "osprofiler trace show --html %s " % trace_id) def run_subcommand(self, argv): @@ -242,16 +253,22 @@ class OpenStackShell(app.App): # osprofiler HMAC key argument if osprofiler_profiler: - parser.add_argument('--profile', - metavar='hmac-key', - help='HMAC key to use for encrypting context ' - 'data for performance profiling of operation. ' - 'This key should be the value of one of the ' - 'HMAC keys configured in osprofiler ' - 'middleware in the projects user would like ' - 'to profile. It needs to be specified in ' - 'configuration files of the required ' - 'projects.') + parser.add_argument( + '--os-profile', + metavar='hmac-key', + dest='profile', + help='HMAC key for encrypting profiling context data', + ) + # NOTE(dtroyer): This global option should have been named + # --os-profile as --profile interferes with at + # least one existing command option. Deprecate + # --profile and remove after Apr 2017. + parser.add_argument( + '--profile', + metavar='hmac-key', + dest='old_profile', + help=argparse.SUPPRESS, + ) return clientmanager.build_plugin_option_parser(parser) diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py index edf29c9b..ba7ee469 100644 --- a/openstackclient/tests/common/test_quota.py +++ b/openstackclient/tests/common/test_quota.py @@ -59,6 +59,7 @@ class TestQuota(compute_fakes.TestComputev2): self.service_catalog_mock = \ self.app.client_manager.auth_ref.service_catalog self.service_catalog_mock.reset_mock() + self.app.client_manager.auth_ref.project_id = identity_fakes.project_id class TestQuotaSet(TestQuota): @@ -304,3 +305,13 @@ class TestQuotaShow(TestQuota): identity_fakes.project_id) self.volume_quotas_class_mock.get.assert_called_with( identity_fakes.project_id) + + def test_quota_show_no_project(self): + parsed_args = self.check_parser(self.cmd, [], []) + + self.cmd.take_action(parsed_args) + + self.quotas_mock.get.assert_called_with(identity_fakes.project_id) + self.volume_quotas_mock.get.assert_called_with( + identity_fakes.project_id) + self.network.get_quota.assert_called_with(identity_fakes.project_id) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 7f39bad0..948d9e97 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -100,8 +100,7 @@ class FakeAggregate(object): :return: A FakeResource object, with id and other attributes """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attribute aggregate_info = { @@ -177,6 +176,9 @@ class FakeComputev2Client(object): self.hosts = mock.Mock() self.hosts.resource_class = fakes.FakeResource(None, {}) + self.server_groups = mock.Mock() + self.server_groups.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -217,7 +219,7 @@ class FakeHypervisor(object): """Fake one or more hypervisor.""" @staticmethod - def create_one_hypervisor(attrs={}): + def create_one_hypervisor(attrs=None): """Create a fake hypervisor. :param Dictionary attrs: @@ -225,6 +227,8 @@ class FakeHypervisor(object): :return: A FakeResource object, with id, hypervisor_hostname, and so on """ + attrs = attrs or {} + # Set default attributes. hypervisor_info = { 'id': 'hypervisor-id-' + uuid.uuid4().hex, @@ -263,7 +267,7 @@ class FakeHypervisor(object): return hypervisor @staticmethod - def create_hypervisors(attrs={}, count=2): + def create_hypervisors(attrs=None, count=2): """Create multiple fake hypervisors. :param Dictionary attrs: @@ -284,7 +288,7 @@ class FakeHypervisorStats(object): """Fake one or more hypervisor stats.""" @staticmethod - def create_one_hypervisor_stats(attrs={}): + def create_one_hypervisor_stats(attrs=None): """Create a fake hypervisor stats. :param Dictionary attrs: @@ -292,6 +296,8 @@ class FakeHypervisorStats(object): :return: A FakeResource object, with id, hypervisor_hostname, and so on """ + attrs = attrs or {} + # Set default attributes. stats_info = { 'count': 2, @@ -319,7 +325,7 @@ class FakeHypervisorStats(object): return hypervisor_stats @staticmethod - def create_hypervisors_stats(attrs={}, count=2): + def create_hypervisors_stats(attrs=None, count=2): """Create multiple fake hypervisors stats. :param Dictionary attrs: @@ -349,8 +355,7 @@ class FakeSecurityGroup(object): :return: A FakeResource object, with id, name, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. security_group_attrs = { @@ -400,8 +405,7 @@ class FakeSecurityGroupRule(object): :return: A FakeResource object, with id, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. security_group_rule_attrs = { @@ -445,7 +449,7 @@ class FakeServer(object): """Fake one or more compute servers.""" @staticmethod - def create_one_server(attrs={}, methods={}): + def create_one_server(attrs=None, methods=None): """Create a fake server. :param Dictionary attrs: @@ -455,6 +459,9 @@ class FakeServer(object): :return: A FakeResource object, with id, name, metadata """ + attrs = attrs or {} + methods = methods or {} + # Set default attributes. server_info = { 'id': 'server-id-' + uuid.uuid4().hex, @@ -477,7 +484,7 @@ class FakeServer(object): return server @staticmethod - def create_servers(attrs={}, methods={}, count=2): + def create_servers(attrs=None, methods=None, count=2): """Create multiple fake servers. :param Dictionary attrs: @@ -527,8 +534,7 @@ class FakeFlavor(object): :return: A FakeResource object, with id, name, ram, vcpus, properties """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. flavor_info = { @@ -566,7 +572,7 @@ class FakeFlavor(object): return flavor @staticmethod - def create_flavors(attrs={}, count=2): + def create_flavors(attrs=None, count=2): """Create multiple fake flavors. :param Dictionary attrs: @@ -614,10 +620,9 @@ class FakeKeypair(object): :return: A FakeResource """ - # Set default attributes. - if attrs is None: - attrs = {} + attrs = attrs or {} + # Set default attributes. keypair_info = { 'name': 'keypair-name-' + uuid.uuid4().hex, 'fingerprint': 'dummy', @@ -658,7 +663,7 @@ class FakeAvailabilityZone(object): """Fake one or more compute availability zones (AZs).""" @staticmethod - def create_one_availability_zone(attrs={}): + def create_one_availability_zone(attrs=None): """Create a fake AZ. :param Dictionary attrs: @@ -666,6 +671,8 @@ class FakeAvailabilityZone(object): :return: A FakeResource object with zoneName, zoneState, etc. """ + attrs = attrs or {} + # Set default attributes. host_name = uuid.uuid4().hex service_name = uuid.uuid4().hex @@ -689,7 +696,7 @@ class FakeAvailabilityZone(object): return availability_zone @staticmethod - def create_availability_zones(attrs={}, count=2): + def create_availability_zones(attrs=None, count=2): """Create multiple fake AZs. :param Dictionary attrs: @@ -712,7 +719,7 @@ class FakeFloatingIP(object): """Fake one or more floating ip.""" @staticmethod - def create_one_floating_ip(attrs={}): + def create_one_floating_ip(attrs=None): """Create a fake floating ip. :param Dictionary attrs: @@ -720,6 +727,8 @@ class FakeFloatingIP(object): :return: A FakeResource object, with id, ip, and so on """ + attrs = attrs or {} + # Set default attributes. floating_ip_attrs = { 'id': 'floating-ip-id-' + uuid.uuid4().hex, @@ -739,7 +748,7 @@ class FakeFloatingIP(object): return floating_ip @staticmethod - def create_floating_ips(attrs={}, count=2): + def create_floating_ips(attrs=None, count=2): """Create multiple fake floating ips. :param Dictionary attrs: @@ -778,7 +787,7 @@ class FakeNetwork(object): """Fake one or more networks.""" @staticmethod - def create_one_network(attrs={}): + def create_one_network(attrs=None): """Create a fake network. :param Dictionary attrs: @@ -786,6 +795,8 @@ class FakeNetwork(object): :return: A FakeResource object, with id, label, cidr and so on """ + attrs = attrs or {} + # Set default attributes. network_attrs = { 'bridge': 'br100', @@ -831,7 +842,7 @@ class FakeNetwork(object): return network @staticmethod - def create_networks(attrs={}, count=2): + def create_networks(attrs=None, count=2): """Create multiple fake networks. :param Dictionary attrs: @@ -860,8 +871,7 @@ class FakeHost(object): :return: A FakeResource object, with id and other attributes """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. host_info = { @@ -899,3 +909,34 @@ class FakeHost(object): info=copy.deepcopy(host_info), loaded=True) return host + + +class FakeServerGroup(object): + """Fake one server group""" + + @staticmethod + def create_one_server_group(attrs=None): + """Create a fake server group + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id and other attributes + """ + if attrs is None: + attrs = {} + + server_group_info = { + 'id': 'server-group-id-' + uuid.uuid4().hex, + 'members': [], + 'metadata': {}, + 'name': 'server-group-name-' + uuid.uuid4().hex, + 'policies': [], + 'project_id': 'server-group-project-id-' + uuid.uuid4().hex, + 'user_id': 'server-group-user-id-' + uuid.uuid4().hex, + } + server_group_info.update(attrs) + server_group = fakes.FakeResource( + info=copy.deepcopy(server_group_info), + loaded=True) + return server_group diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 03ca8807..fa29111b 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -288,8 +288,10 @@ class TestFlavorSet(TestFlavor): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - - self.flavors_mock.find.assert_called_with(name='baremetal') + try: + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor) + except Exception: + self.flavors_mock.get.assert_called_with(parsed_args.flavor) self.assertIsNone(result) @@ -382,6 +384,8 @@ class TestFlavorUnset(TestFlavor): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - - self.flavors_mock.find.assert_called_with(name='baremetal') + try: + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor) + except Exception: + self.flavors_mock.get.assert_called_with(parsed_args.flavor) self.assertIsNone(result) diff --git a/openstackclient/tests/compute/v2/test_server_group.py b/openstackclient/tests/compute/v2/test_server_group.py new file mode 100644 index 00000000..70ff23f9 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_server_group.py @@ -0,0 +1,283 @@ +# Copyright 2016 Huawei, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import mock + +from openstackclient.common import exceptions +from openstackclient.common import utils +from openstackclient.compute.v2 import server_group +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import utils as tests_utils + + +class TestServerGroup(compute_fakes.TestComputev2): + + fake_server_group = compute_fakes.FakeServerGroup.create_one_server_group() + + columns = ( + 'id', + 'members', + 'name', + 'policies', + 'project_id', + 'user_id', + ) + + data = ( + fake_server_group.id, + utils.format_list(fake_server_group.members), + fake_server_group.name, + utils.format_list(fake_server_group.policies), + fake_server_group.project_id, + fake_server_group.user_id, + ) + + def setUp(self): + super(TestServerGroup, self).setUp() + + # Get a shortcut to the ServerGroupsManager Mock + self.server_groups_mock = self.app.client_manager.compute.server_groups + self.server_groups_mock.reset_mock() + + +class TestServerGroupCreate(TestServerGroup): + + def setUp(self): + super(TestServerGroupCreate, self).setUp() + + self.server_groups_mock.create.return_value = self.fake_server_group + self.cmd = server_group.CreateServerGroup(self.app, None) + + def test_server_group_create(self): + arglist = [ + '--policy', 'affinity', + 'affinity_group', + ] + verifylist = [ + ('policy', ['affinity']), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.create.assert_called_once_with( + name=parsed_args.name, + policies=parsed_args.policy, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_server_group_create_with_multiple_policies(self): + arglist = [ + '--policy', 'affinity', + '--policy', 'soft-affinity', + 'affinity_group', + ] + verifylist = [ + ('policy', ['affinity', 'soft-affinity']), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.create.assert_called_once_with( + name=parsed_args.name, + policies=parsed_args.policy, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_server_group_create_no_policy(self): + arglist = [ + 'affinity_group', + ] + verifylist = None + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + + +class TestServerGroupDelete(TestServerGroup): + + def setUp(self): + super(TestServerGroupDelete, self).setUp() + + self.server_groups_mock.get.return_value = self.fake_server_group + self.cmd = server_group.DeleteServerGroup(self.app, None) + + def test_server_group_delete(self): + arglist = [ + 'affinity_group', + ] + verifylist = [ + ('server_group', ['affinity_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.server_groups_mock.get.assert_called_once_with('affinity_group') + self.server_groups_mock.delete.assert_called_once_with( + self.fake_server_group.id + ) + self.assertIsNone(result) + + def test_server_group_multiple_delete(self): + arglist = [ + 'affinity_group', + 'anti_affinity_group' + ] + verifylist = [ + ('server_group', ['affinity_group', 'anti_affinity_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.server_groups_mock.get.assert_any_call('affinity_group') + self.server_groups_mock.get.assert_any_call('anti_affinity_group') + self.server_groups_mock.delete.assert_called_with( + self.fake_server_group.id + ) + self.assertEqual(2, self.server_groups_mock.get.call_count) + self.assertEqual(2, self.server_groups_mock.delete.call_count) + self.assertIsNone(result) + + def test_server_group_delete_no_input(self): + arglist = [] + verifylist = None + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + + def test_server_group_multiple_delete_with_exception(self): + arglist = [ + 'affinity_group', + 'anti_affinity_group' + ] + verifylist = [ + ('server_group', ['affinity_group', 'anti_affinity_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + find_mock_result = [self.fake_server_group, exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 server groups failed to delete.', + str(e)) + + find_mock.assert_any_call(self.server_groups_mock, + 'affinity_group') + find_mock.assert_any_call(self.server_groups_mock, + 'anti_affinity_group') + + self.assertEqual(2, find_mock.call_count) + self.server_groups_mock.delete.assert_called_once_with( + self.fake_server_group.id + ) + + +class TestServerGroupList(TestServerGroup): + + list_columns = ( + 'ID', + 'Name', + 'Policies', + ) + + list_columns_long = ( + 'ID', + 'Name', + 'Policies', + 'Members', + 'Project Id', + 'User Id', + ) + + list_data = (( + TestServerGroup.fake_server_group.id, + TestServerGroup.fake_server_group.name, + utils.format_list(TestServerGroup.fake_server_group.policies), + ),) + + list_data_long = (( + TestServerGroup.fake_server_group.id, + TestServerGroup.fake_server_group.name, + utils.format_list(TestServerGroup.fake_server_group.policies), + utils.format_list(TestServerGroup.fake_server_group.members), + TestServerGroup.fake_server_group.project_id, + TestServerGroup.fake_server_group.user_id, + ),) + + def setUp(self): + super(TestServerGroupList, self).setUp() + + self.server_groups_mock.list.return_value = [self.fake_server_group] + self.cmd = server_group.ListServerGroup(self.app, None) + + def test_server_group_list(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.list.assert_called_once_with(False) + + self.assertEqual(self.list_columns, columns) + self.assertEqual(self.list_data, tuple(data)) + + def test_server_group_list_with_all_projects_and_long(self): + arglist = [ + '--all-projects', + '--long', + ] + verifylist = [ + ('all_projects', True), + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.list.assert_called_once_with(True) + + self.assertEqual(self.list_columns_long, columns) + self.assertEqual(self.list_data_long, tuple(data)) + + +class TestServerGroupShow(TestServerGroup): + + def setUp(self): + super(TestServerGroupShow, self).setUp() + + self.server_groups_mock.get.return_value = self.fake_server_group + self.cmd = server_group.ShowServerGroup(self.app, None) + + def test_server_group_show(self): + arglist = [ + 'affinity_group', + ] + verifylist = [ + ('server_group', 'affinity_group'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index f0cebb06..46f983dc 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -142,7 +142,7 @@ class FakeModule(object): class FakeResource(object): - def __init__(self, manager=None, info={}, loaded=False, methods={}): + def __init__(self, manager=None, info=None, loaded=False, methods=None): """Set attributes and methods for a resource. :param manager: @@ -154,6 +154,9 @@ class FakeResource(object): :param Dictionary methods: A dictionary with all methods """ + info = info or {} + methods = methods or {} + self.__name__ = type(self).__name__ self.manager = manager self._info = info @@ -189,9 +192,12 @@ class FakeResource(object): class FakeResponse(requests.Response): - def __init__(self, headers={}, status_code=200, data=None, encoding=None): + def __init__(self, headers=None, status_code=200, + data=None, encoding=None): super(FakeResponse, self).__init__() + headers = headers or {} + self.status_code = status_code self.headers.update(headers) diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 3555d2d4..f90d846d 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -181,7 +181,7 @@ class FakeImage(object): """ @staticmethod - def create_one_image(attrs={}): + def create_one_image(attrs=None): """Create a fake image. :param Dictionary attrs: @@ -190,6 +190,8 @@ class FakeImage(object): A FakeResource object with id, name, owner, protected, visibility and tags attrs """ + attrs = attrs or {} + # Set default attribute image_info = { 'id': 'image-id' + uuid.uuid4().hex, @@ -210,7 +212,7 @@ class FakeImage(object): return image @staticmethod - def create_images(attrs={}, count=2): + def create_images(attrs=None, count=2): """Create multiple fake images. :param Dictionary attrs: diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 73c6b09e..1989b515 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -83,8 +83,7 @@ class FakeAddressScope(object): :return: A FakeResource object with name, id, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. address_scope_attrs = { @@ -112,7 +111,7 @@ class FakeAvailabilityZone(object): """Fake one or more network availability zones (AZs).""" @staticmethod - def create_one_availability_zone(attrs={}): + def create_one_availability_zone(attrs=None): """Create a fake AZ. :param Dictionary attrs: @@ -120,6 +119,8 @@ class FakeAvailabilityZone(object): :return: A FakeResource object with name, state, etc. """ + attrs = attrs or {} + # Set default attributes. availability_zone = { 'name': uuid.uuid4().hex, @@ -136,7 +137,7 @@ class FakeAvailabilityZone(object): return availability_zone @staticmethod - def create_availability_zones(attrs={}, count=2): + def create_availability_zones(attrs=None, count=2): """Create multiple fake AZs. :param Dictionary attrs: @@ -159,7 +160,7 @@ class FakeNetwork(object): """Fake one or more networks.""" @staticmethod - def create_one_network(attrs={}): + def create_one_network(attrs=None): """Create a fake network. :param Dictionary attrs: @@ -168,6 +169,8 @@ class FakeNetwork(object): A FakeResource object, with id, name, admin_state_up, router_external, status, subnets, tenant_id """ + attrs = attrs or {} + # Set default attributes. network_attrs = { 'id': 'network-id-' + uuid.uuid4().hex, @@ -196,7 +199,7 @@ class FakeNetwork(object): return network @staticmethod - def create_networks(attrs={}, count=2): + def create_networks(attrs=None, count=2): """Create multiple fake networks. :param Dictionary attrs: @@ -236,16 +239,16 @@ class FakePort(object): """Fake one or more ports.""" @staticmethod - def create_one_port(attrs={}): + def create_one_port(attrs=None): """Create a fake port. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object, with id, name, etc. """ + attrs = attrs or {} + # Set default attributes. port_attrs = { 'admin_state_up': True, @@ -288,7 +291,7 @@ class FakePort(object): return port @staticmethod - def create_ports(attrs={}, count=2): + def create_ports(attrs=None, count=2): """Create multiple fake ports. :param Dictionary attrs: @@ -414,8 +417,7 @@ class FakeSecurityGroup(object): :return: A FakeResource object, with id, name, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. security_group_attrs = { @@ -469,8 +471,7 @@ class FakeSecurityGroupRule(object): :return: A FakeResource object, with id, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. security_group_rule_attrs = { @@ -521,7 +522,7 @@ class FakeSubnet(object): """Fake one or more subnets.""" @staticmethod - def create_one_subnet(attrs={}): + def create_one_subnet(attrs=None): """Create a fake subnet. :param Dictionary attrs: @@ -529,6 +530,8 @@ class FakeSubnet(object): :return: A FakeResource object faking the subnet """ + attrs = attrs or {} + # Set default attributes. project_id = 'project-id-' + uuid.uuid4().hex subnet_attrs = { @@ -553,13 +556,14 @@ class FakeSubnet(object): subnet = fakes.FakeResource(info=copy.deepcopy(subnet_attrs), loaded=True) + # Set attributes with special mappings in OpenStack SDK. subnet.project_id = subnet_attrs['tenant_id'] return subnet @staticmethod - def create_subnets(attrs={}, count=2): + def create_subnets(attrs=None, count=2): """Create multiple fake subnets. :param Dictionary attrs: @@ -580,7 +584,7 @@ class FakeFloatingIP(object): """Fake one or more floating ip.""" @staticmethod - def create_one_floating_ip(attrs={}): + def create_one_floating_ip(attrs=None): """Create a fake floating ip. :param Dictionary attrs: @@ -588,6 +592,8 @@ class FakeFloatingIP(object): :return: A FakeResource object, with id, ip, and so on """ + attrs = attrs or {} + # Set default attributes. floating_ip_attrs = { 'id': 'floating-ip-id-' + uuid.uuid4().hex, @@ -616,7 +622,7 @@ class FakeFloatingIP(object): return floating_ip @staticmethod - def create_floating_ips(attrs={}, count=2): + def create_floating_ips(attrs=None, count=2): """Create multiple fake floating ips. :param Dictionary attrs: @@ -655,7 +661,7 @@ class FakeSubnetPool(object): """Fake one or more subnet pools.""" @staticmethod - def create_one_subnet_pool(attrs={}): + def create_one_subnet_pool(attrs=None): """Create a fake subnet pool. :param Dictionary attrs: @@ -663,6 +669,8 @@ class FakeSubnetPool(object): :return: A FakeResource object faking the subnet pool """ + attrs = attrs or {} + # Set default attributes. subnet_pool_attrs = { 'id': 'subnet-pool-id-' + uuid.uuid4().hex, @@ -693,7 +701,7 @@ class FakeSubnetPool(object): return subnet_pool @staticmethod - def create_subnet_pools(attrs={}, count=2): + def create_subnet_pools(attrs=None, count=2): """Create multiple fake subnet pools. :param Dictionary attrs: diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 7d0f8717..a1b0aec9 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -484,6 +484,9 @@ class TestSetNetwork(TestNetwork): '--share', '--external', '--default', + '--provider-network-type', 'vlan', + '--provider-physical-network', 'physnet1', + '--provider-segment', '400', ] verifylist = [ ('network', self._network.name), @@ -492,6 +495,9 @@ class TestSetNetwork(TestNetwork): ('share', True), ('external', True), ('default', True), + ('provider_network_type', 'vlan'), + ('physical_network', 'physnet1'), + ('segmentation_id', '400'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -503,6 +509,9 @@ class TestSetNetwork(TestNetwork): 'shared': True, 'router:external': True, 'is_default': True, + 'provider:network_type': 'vlan', + 'provider:physical_network': 'physnet1', + 'provider:segmentation_id': '400', } self.network.update_network.assert_called_once_with( self._network, **attrs) diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index c2fa1256..df7414aa 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -532,31 +532,46 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): _security_group_rules = [_security_group_rule_tcp, _security_group_rule_icmp] - expected_columns_with_group = ( + expected_columns_with_group_and_long = ( 'ID', 'IP Protocol', 'IP Range', 'Port Range', + 'Direction', + 'Ethertype', 'Remote Security Group', ) - expected_columns_no_group = \ - expected_columns_with_group + ('Security Group',) + expected_columns_no_group = ( + 'ID', + 'IP Protocol', + 'IP Range', + 'Port Range', + 'Remote Security Group', + 'Security Group', + ) - expected_data_with_group = [] + expected_data_with_group_and_long = [] expected_data_no_group = [] for _security_group_rule in _security_group_rules: - expected_rule_with_group = ( + expected_data_with_group_and_long.append(( _security_group_rule.id, _security_group_rule.protocol, _security_group_rule.remote_ip_prefix, security_group_rule._format_network_port_range( _security_group_rule), + _security_group_rule.direction, + _security_group_rule.ethertype, _security_group_rule.remote_group_id, - ) - expected_rule_no_group = expected_rule_with_group + \ - (_security_group_rule.security_group_id,) - expected_data_with_group.append(expected_rule_with_group) - expected_data_no_group.append(expected_rule_no_group) + )) + expected_data_no_group.append(( + _security_group_rule.id, + _security_group_rule.protocol, + _security_group_rule.remote_ip_prefix, + security_group_rule._format_network_port_range( + _security_group_rule), + _security_group_rule.remote_group_id, + _security_group_rule.security_group_id, + )) def setUp(self): super(TestListSecurityGroupRuleNetwork, self).setUp() @@ -570,7 +585,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.cmd = security_group_rule.ListSecurityGroupRule( self.app, self.namespace) - def test_list_no_group(self): + def test_list_default(self): self._security_group_rule_tcp.port_range_min = 80 parsed_args = self.check_parser(self.cmd, [], []) @@ -580,12 +595,14 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.assertEqual(self.expected_columns_no_group, columns) self.assertEqual(self.expected_data_no_group, list(data)) - def test_list_with_group(self): + def test_list_with_group_and_long(self): self._security_group_rule_tcp.port_range_min = 80 arglist = [ + '--long', self._security_group.id, ] verifylist = [ + ('long', True), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -595,8 +612,24 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.security_group_rules.assert_called_once_with(**{ 'security_group_id': self._security_group.id, }) - self.assertEqual(self.expected_columns_with_group, columns) - self.assertEqual(self.expected_data_with_group, list(data)) + self.assertEqual(self.expected_columns_with_group_and_long, columns) + self.assertEqual(self.expected_data_with_group_and_long, list(data)) + + def test_list_with_ignored_options(self): + self._security_group_rule_tcp.port_range_min = 80 + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_group_rules.assert_called_once_with(**{}) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): @@ -665,11 +698,13 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): # Get the command object to test self.cmd = security_group_rule.ListSecurityGroupRule(self.app, None) - def test_list_no_group(self): + def test_list_default(self): parsed_args = self.check_parser(self.cmd, [], []) columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.list.assert_called_once_with() + self.compute.security_groups.list.assert_called_once_with( + search_opts={'all_tenants': False} + ) self.assertEqual(self.expected_columns_no_group, columns) self.assertEqual(self.expected_data_no_group, list(data)) @@ -689,6 +724,38 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): self.assertEqual(self.expected_columns_with_group, columns) self.assertEqual(self.expected_data_with_group, list(data)) + def test_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.compute.security_groups.list.assert_called_once_with( + search_opts={'all_tenants': True} + ) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + + def test_list_with_ignored_options(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.compute.security_groups.list.assert_called_once_with( + search_opts={'all_tenants': False} + ) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index cbb32fc3..7797e4d0 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -113,6 +113,17 @@ class TestCreateSubnetPool(TestSubnetPool): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + def test_create_no_pool_prefix(self): + """Make sure --pool-prefix is a required argument""" + arglist = [ + self._subnet_pool.name, + ] + verifylist = [ + ('name', self._subnet_pool.name), + ] + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + def test_create_default_options(self): arglist = [ '--pool-prefix', '10.0.10.0/24', @@ -138,23 +149,26 @@ class TestCreateSubnetPool(TestSubnetPool): '--default-prefix-length', self._subnet_pool.default_prefixlen, '--max-prefix-length', self._subnet_pool.max_prefixlen, '--min-prefix-length', self._subnet_pool.min_prefixlen, + '--pool-prefix', '10.0.10.0/24', self._subnet_pool.name, ] verifylist = [ - ('default_prefix_length', self._subnet_pool.default_prefixlen), - ('max_prefix_length', self._subnet_pool.max_prefixlen), - ('min_prefix_length', self._subnet_pool.min_prefixlen), + ('default_prefix_length', + int(self._subnet_pool.default_prefixlen)), + ('max_prefix_length', int(self._subnet_pool.max_prefixlen)), + ('min_prefix_length', int(self._subnet_pool.min_prefixlen)), ('name', self._subnet_pool.name), + ('prefixes', ['10.0.10.0/24']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = (self.cmd.take_action(parsed_args)) self.network.create_subnet_pool.assert_called_once_with(**{ - 'default_prefixlen': self._subnet_pool.default_prefixlen, - 'max_prefixlen': self._subnet_pool.max_prefixlen, - 'min_prefixlen': self._subnet_pool.min_prefixlen, - 'prefixes': [], + 'default_prefixlen': int(self._subnet_pool.default_prefixlen), + 'max_prefixlen': int(self._subnet_pool.max_prefixlen), + 'min_prefixlen': int(self._subnet_pool.min_prefixlen), + 'prefixes': ['10.0.10.0/24'], 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) @@ -221,6 +235,32 @@ class TestCreateSubnetPool(TestSubnetPool): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_default_and_shared_options(self): + arglist = [ + '--pool-prefix', '10.0.10.0/24', + '--default', + '--share', + self._subnet_pool.name, + ] + verifylist = [ + ('prefixes', ['10.0.10.0/24']), + ('default', True), + ('share', True), + ('name', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_subnet_pool.assert_called_once_with(**{ + 'is_default': True, + 'name': self._subnet_pool.name, + 'prefixes': ['10.0.10.0/24'], + 'shared': True, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteSubnetPool(TestSubnetPool): @@ -267,6 +307,8 @@ class TestListSubnetPool(TestSubnetPool): columns_long = columns + ( 'Default Prefix Length', 'Address Scope', + 'Default Subnet Pool', + 'Shared', ) data = [] @@ -285,6 +327,8 @@ class TestListSubnetPool(TestSubnetPool): utils.format_list(pool.prefixes), pool.default_prefixlen, pool.address_scope_id, + pool.is_default, + pool.shared, )) def setUp(self): @@ -354,8 +398,8 @@ class TestSetSubnetPool(TestSubnetPool): ] verifylist = [ ('name', 'noob'), - ('default_prefix_length', '8'), - ('min_prefix_length', '8'), + ('default_prefix_length', 8), + ('min_prefix_length', 8), ('subnet_pool', self._subnet_pool.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -364,8 +408,8 @@ class TestSetSubnetPool(TestSubnetPool): attrs = { 'name': 'noob', - 'default_prefixlen': '8', - 'min_prefixlen': '8', + 'default_prefixlen': 8, + 'min_prefixlen': 8, } self.network.update_subnet_pool.assert_called_once_with( self._subnet_pool, **attrs) @@ -380,7 +424,7 @@ class TestSetSubnetPool(TestSubnetPool): ] verifylist = [ ('prefixes', ['10.0.1.0/24', '10.0.2.0/24']), - ('max_prefix_length', '16'), + ('max_prefix_length', 16), ('subnet_pool', self._subnet_pool.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -391,7 +435,7 @@ class TestSetSubnetPool(TestSubnetPool): prefixes.extend(self._subnet_pool.prefixes) attrs = { 'prefixes': prefixes, - 'max_prefixlen': '16', + 'max_prefixlen': 16, } self.network.update_subnet_pool.assert_called_once_with( self._subnet_pool, **attrs) @@ -474,6 +518,62 @@ class TestSetSubnetPool(TestSubnetPool): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + def test_set_default(self): + arglist = [ + '--default', + self._subnet_pool.name, + ] + verifylist = [ + ('default', True), + ('subnet_pool', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'is_default': True + } + self.network.update_subnet_pool.assert_called_once_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) + + def test_set_no_default(self): + arglist = [ + '--no-default', + self._subnet_pool.name, + ] + verifylist = [ + ('no_default', True), + ('subnet_pool', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'is_default': False, + } + self.network.update_subnet_pool.assert_called_once_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) + + def test_set_no_default_conflict(self): + arglist = [ + '--default', + '--no-default', + self._subnet_pool.name, + ] + verifylist = [ + ('default', True), + ('no_default', True), + ('subnet_pool', self._subnet_pool.name), + ] + + # Exclusive arguments will conflict here. + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + class TestShowSubnetPool(TestSubnetPool): diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index ab97dd91..90454fc2 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -112,7 +112,7 @@ global_options = { '--os-default-domain': (DEFAULT_DOMAIN_NAME, True, True), '--os-cacert': ('/dev/null', True, True), '--timing': (True, True, False), - '--profile': ('SECRET_KEY', True, False), + '--os-profile': ('SECRET_KEY', True, False), '--os-interface': (DEFAULT_INTERFACE, True, True) } diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/volume/v1/fakes.py index 42673efa..d6c46439 100644 --- a/openstackclient/tests/volume/v1/fakes.py +++ b/openstackclient/tests/volume/v1/fakes.py @@ -129,6 +129,97 @@ QOS_WITH_ASSOCIATIONS = { } +class FakeServiceClient(object): + + def __init__(self, **kwargs): + self.services = mock.Mock() + self.services.resource_class = fakes.FakeResource(None, {}) + + +class TestService(utils.TestCommand): + + def setUp(self): + super(TestService, self).setUp() + + self.app.client_manager.volume = FakeServiceClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + + +class FakeService(object): + """Fake one or more Services.""" + + @staticmethod + def create_one_service(attrs=None): + """Create a fake service. + + :param Dictionary attrs: + A dictionary with all attributes of service + :retrun: + A FakeResource object with host, status, etc. + """ + # Set default attribute + service_info = { + 'host': 'host_test', + 'binary': 'cinder_test', + 'status': 'enabled', + 'disabled_reason': 'LongHoliday-GoldenWeek', + 'zone': 'fake_zone', + 'updated_at': 'fake_date', + 'state': 'fake_state', + } + + # Overwrite default attributes if there are some attributes set + if attrs is None: + attrs = {} + service_info.update(attrs) + + service = fakes.FakeResource( + None, + service_info, + loaded=True) + + return service + + @staticmethod + def create_services(attrs=None, count=2): + """Create multiple fake services. + + :param Dictionary attrs: + A dictionary with all attributes of service + :param Integer count: + The number of services to be faked + :return: + A list of FakeResource objects + """ + services = [] + for n in range(0, count): + services.append(FakeService.create_one_service(attrs)) + + return services + + @staticmethod + def get_services(services=None, count=2): + """Get an iterable MagicMock object with a list of faked services. + + If services list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List services: + A list of FakeResource objects faking services + :param Integer count: + The number of services to be faked + :return + An iterable Mock object with side_effect set to a list of faked + services + """ + if services is None: + services = FakeService.create_services(count) + + return mock.MagicMock(side_effect=services) + + class FakeImagev1Client(object): def __init__(self, **kwargs): diff --git a/openstackclient/tests/volume/v1/test_service.py b/openstackclient/tests/volume/v1/test_service.py new file mode 100644 index 00000000..71684344 --- /dev/null +++ b/openstackclient/tests/volume/v1/test_service.py @@ -0,0 +1,141 @@ +# +# 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 service_fakes +from openstackclient.volume.v1 import service + + +class TestService(service_fakes.TestService): + + def setUp(self): + super(TestService, self).setUp() + + # Get a shortcut to the ServiceManager Mock + self.service_mock = self.app.client_manager.volume.services + self.service_mock.reset_mock() + + +class TestServiceList(TestService): + + # The service to be listed + services = service_fakes.FakeService.create_one_service() + + def setUp(self): + super(TestServiceList, self).setUp() + + self.service_mock.list.return_value = [self.services] + + # Get the command object to test + self.cmd = service.ListService(self.app, None) + + def test_service_list(self): + arglist = [ + '--host', self.services.host, + '--service', self.services.binary, + ] + verifylist = [ + ('host', self.services.host), + ('service', self.services.binary), + ] + 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 = [ + 'Binary', + 'Host', + 'Zone', + 'Status', + 'State', + 'Updated At', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.services.binary, + self.services.host, + self.services.zone, + self.services.status, + self.services.state, + self.services.updated_at, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list services + self.service_mock.list.assert_called_with( + self.services.host, + self.services.binary, + ) + + # checking if prohibited columns are present in output + self.assertNotIn("Disabled Reason", columns) + self.assertNotIn(self.services.disabled_reason, + tuple(data)) + + def test_service_list_with_long_option(self): + arglist = [ + '--host', self.services.host, + '--service', self.services.binary, + '--long' + ] + verifylist = [ + ('host', self.services.host), + ('service', self.services.binary), + ('long', 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 = [ + 'Binary', + 'Host', + 'Zone', + 'Status', + 'State', + 'Updated At', + 'Disabled Reason' + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.services.binary, + self.services.host, + self.services.zone, + self.services.status, + self.services.state, + self.services.updated_at, + self.services.disabled_reason, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + self.service_mock.list.assert_called_with( + self.services.host, + self.services.binary, + ) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 3c238d10..120666a0 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -232,6 +232,97 @@ EXTENSION = { } +class FakeServiceClient(object): + + def __init__(self, **kwargs): + self.services = mock.Mock() + self.services.resource_class = fakes.FakeResource(None, {}) + + +class TestService(utils.TestCommand): + + def setUp(self): + super(TestService, self).setUp() + + self.app.client_manager.volume = FakeServiceClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + + +class FakeService(object): + """Fake one or more Services.""" + + @staticmethod + def create_one_service(attrs=None): + """Create a fake service. + + :param Dictionary attrs: + A dictionary with all attributes of service + :retrun: + A FakeResource object with host, status, etc. + """ + # Set default attribute + service_info = { + 'host': 'host_test', + 'binary': 'cinder_test', + 'status': 'enabled', + 'disabled_reason': 'LongHoliday-GoldenWeek', + 'zone': 'fake_zone', + 'updated_at': 'fake_date', + 'state': 'fake_state', + } + + # Overwrite default attributes if there are some attributes set + if attrs is None: + attrs = {} + service_info.update(attrs) + + service = fakes.FakeResource( + None, + service_info, + loaded=True) + + return service + + @staticmethod + def create_services(attrs=None, count=2): + """Create multiple fake services. + + :param Dictionary attrs: + A dictionary with all attributes of service + :param Integer count: + The number of services to be faked + :return: + A list of FakeResource objects + """ + services = [] + for n in range(0, count): + services.append(FakeService.create_one_service(attrs)) + + return services + + @staticmethod + def get_services(services=None, count=2): + """Get an iterable MagicMock object with a list of faked services. + + If services list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List services: + A list of FakeResource objects faking services + :param Integer count: + The number of services to be faked + :return + An iterable Mock object with side_effect set to a list of faked + services + """ + if services is None: + services = FakeService.create_services(count) + + return mock.MagicMock(side_effect=services) + + class FakeVolumeClient(object): def __init__(self, **kwargs): @@ -281,7 +372,7 @@ class FakeVolume(object): """ @staticmethod - def create_one_volume(attrs={}): + def create_one_volume(attrs=None): """Create a fake volume. :param Dictionary attrs: @@ -289,6 +380,8 @@ class FakeVolume(object): :retrun: A FakeResource object with id, name, status, etc. """ + attrs = attrs or {} + # Set default attribute volume_info = { 'id': 'volume-id' + uuid.uuid4().hex, @@ -320,7 +413,7 @@ class FakeVolume(object): return volume @staticmethod - def create_volumes(attrs={}, count=2): + def create_volumes(attrs=None, count=2): """Create multiple fake volumes. :param Dictionary attrs: @@ -361,16 +454,16 @@ class FakeAvailabilityZone(object): """Fake one or more volume availability zones (AZs).""" @staticmethod - def create_one_availability_zone(attrs={}, methods={}): + def create_one_availability_zone(attrs=None): """Create a fake AZ. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object with zoneName, zoneState, etc. """ + attrs = attrs or {} + # Set default attributes. availability_zone = { 'zoneName': uuid.uuid4().hex, @@ -382,18 +475,15 @@ class FakeAvailabilityZone(object): availability_zone = fakes.FakeResource( info=copy.deepcopy(availability_zone), - methods=methods, loaded=True) return availability_zone @staticmethod - def create_availability_zones(attrs={}, methods={}, count=2): + def create_availability_zones(attrs=None, count=2): """Create multiple fake AZs. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of AZs to fake :return: @@ -402,8 +492,7 @@ class FakeAvailabilityZone(object): availability_zones = [] for i in range(0, count): availability_zone = \ - FakeAvailabilityZone.create_one_availability_zone( - attrs, methods) + FakeAvailabilityZone.create_one_availability_zone(attrs) availability_zones.append(availability_zone) return availability_zones diff --git a/openstackclient/tests/volume/v2/test_service.py b/openstackclient/tests/volume/v2/test_service.py new file mode 100644 index 00000000..ba2e1b32 --- /dev/null +++ b/openstackclient/tests/volume/v2/test_service.py @@ -0,0 +1,141 @@ +# +# 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 service_fakes +from openstackclient.volume.v2 import service + + +class TestService(service_fakes.TestService): + + def setUp(self): + super(TestService, self).setUp() + + # Get a shortcut to the ServiceManager Mock + self.service_mock = self.app.client_manager.volume.services + self.service_mock.reset_mock() + + +class TestServiceList(TestService): + + # The service to be listed + services = service_fakes.FakeService.create_one_service() + + def setUp(self): + super(TestServiceList, self).setUp() + + self.service_mock.list.return_value = [self.services] + + # Get the command object to test + self.cmd = service.ListService(self.app, None) + + def test_service_list(self): + arglist = [ + '--host', self.services.host, + '--service', self.services.binary, + ] + verifylist = [ + ('host', self.services.host), + ('service', self.services.binary), + ] + 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 = [ + 'Binary', + 'Host', + 'Zone', + 'Status', + 'State', + 'Updated At', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.services.binary, + self.services.host, + self.services.zone, + self.services.status, + self.services.state, + self.services.updated_at, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list services + self.service_mock.list.assert_called_with( + self.services.host, + self.services.binary, + ) + + # checking if prohibited columns are present in output + self.assertNotIn("Disabled Reason", columns) + self.assertNotIn(self.services.disabled_reason, + tuple(data)) + + def test_service_list_with_long_option(self): + arglist = [ + '--host', self.services.host, + '--service', self.services.binary, + '--long' + ] + verifylist = [ + ('host', self.services.host), + ('service', self.services.binary), + ('long', 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 = [ + 'Binary', + 'Host', + 'Zone', + 'Status', + 'State', + 'Updated At', + 'Disabled Reason' + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.services.binary, + self.services.host, + self.services.zone, + self.services.status, + self.services.state, + self.services.updated_at, + self.services.disabled_reason, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + self.service_mock.list.assert_called_with( + self.services.host, + self.services.binary, + ) diff --git a/openstackclient/volume/v1/service.py b/openstackclient/volume/v1/service.py new file mode 100644 index 00000000..f26be13e --- /dev/null +++ b/openstackclient/volume/v1/service.py @@ -0,0 +1,70 @@ +# +# 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. +# + +"""Service action implementations""" + +from openstackclient.common import command +from openstackclient.common import utils + + +class ListService(command.Lister): + """List service command""" + + def get_parser(self, prog_name): + parser = super(ListService, self).get_parser(prog_name) + parser.add_argument( + "--host", + metavar="<host>", + help="List services on specified host (name only)") + parser.add_argument( + "--service", + metavar="<service>", + help="List only specified service (name only)") + parser.add_argument( + "--long", + action="store_true", + default=False, + help="List additional fields in output" + ) + return parser + + def take_action(self, parsed_args): + service_client = self.app.client_manager.volume + + if parsed_args.long: + columns = [ + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At", + "Disabled Reason" + ] + else: + columns = [ + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At" + ] + + data = service_client.services.list(parsed_args.host, + parsed_args.service) + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) diff --git a/openstackclient/volume/v2/service.py b/openstackclient/volume/v2/service.py new file mode 100644 index 00000000..f26be13e --- /dev/null +++ b/openstackclient/volume/v2/service.py @@ -0,0 +1,70 @@ +# +# 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. +# + +"""Service action implementations""" + +from openstackclient.common import command +from openstackclient.common import utils + + +class ListService(command.Lister): + """List service command""" + + def get_parser(self, prog_name): + parser = super(ListService, self).get_parser(prog_name) + parser.add_argument( + "--host", + metavar="<host>", + help="List services on specified host (name only)") + parser.add_argument( + "--service", + metavar="<service>", + help="List only specified service (name only)") + parser.add_argument( + "--long", + action="store_true", + default=False, + help="List additional fields in output" + ) + return parser + + def take_action(self, parsed_args): + service_client = self.app.client_manager.volume + + if parsed_args.long: + columns = [ + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At", + "Disabled Reason" + ] + else: + columns = [ + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At" + ] + + data = service_client.services.list(parsed_args.host, + parsed_args.service) + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) |
