From aa2dad3d8335b3055c04be38f179cec7f2923904 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Fri, 4 Dec 2015 01:48:01 +0900 Subject: [PATCH 01/57] Setup for translation client translation effort starts and several client projects including novaclient already setup translations. To start translation, we need to initially import the translation file - and place it at the proper place so that the usual CI scripts can handle it. The proper place is for all python projects $PROJECT/locale/$PROJECT.pot - see setup.cfg. Further imports will be done by the OpenStack Proposal bot. Closes-Bug: #1099603 Change-Id: I5b13a9428e6272e83a45b82c06f524be772d3d7a --- babel.cfg | 2 + .../locale/python-neutronclient.pot | 1768 +++++++++++++++++ setup.cfg | 14 + 3 files changed, 1784 insertions(+) create mode 100644 babel.cfg create mode 100644 python-neutronclient/locale/python-neutronclient.pot diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..15cd6cb --- /dev/null +++ b/babel.cfg @@ -0,0 +1,2 @@ +[python: **.py] + diff --git a/python-neutronclient/locale/python-neutronclient.pot b/python-neutronclient/locale/python-neutronclient.pot new file mode 100644 index 0000000..94216dd --- /dev/null +++ b/python-neutronclient/locale/python-neutronclient.pot @@ -0,0 +1,1768 @@ +# Translations template for python-neutronclient. +# Copyright (C) 2015 ORGANIZATION +# This file is distributed under the same license as the +# python-neutronclient project. +# FIRST AUTHOR , 2015. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: python-neutronclient 3.1.1.dev64\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2015-12-03 21:54+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.1.1\n" + +#: neutronclient/client.py:230 +msgid "" +"For \"noauth\" authentication strategy, the endpoint must be specified " +"either in the constructor or using --os-url" +msgstr "" + +#: neutronclient/client.py:241 +#, python-format +msgid "Unknown auth strategy: %s" +msgstr "" + +#: neutronclient/shell.py:136 +#, python-format +msgid "invalid int value: %r" +msgstr "" + +#: neutronclient/shell.py:138 +#, python-format +msgid "input value %d is negative" +msgstr "" + +#: neutronclient/shell.py:410 +#, python-format +msgid "" +"\n" +"Commands for API v%s:\n" +msgstr "" + +#: neutronclient/shell.py:475 +msgid "" +"Increase verbosity of output and show tracebacks on errors. You can " +"repeat this option." +msgstr "" + +#: neutronclient/shell.py:482 +msgid "Suppress output except warnings and errors." +msgstr "" + +#: neutronclient/shell.py:488 +msgid "Show this help message and exit." +msgstr "" + +#: neutronclient/shell.py:494 +msgid "" +"How many times the request to the Neutron server should be retried if it " +"fails." +msgstr "" + +#: neutronclient/shell.py:514 +msgid "Defaults to env[OS_NETWORK_SERVICE_TYPE] or network." +msgstr "" + +#: neutronclient/shell.py:519 +msgid "Defaults to env[OS_ENDPOINT_TYPE] or public." +msgstr "" + +#: neutronclient/shell.py:526 +msgid "DEPRECATED! Use --os-service-type." +msgstr "" + +#: neutronclient/shell.py:533 +msgid "DEPRECATED! Use --os-endpoint-type." +msgstr "" + +#: neutronclient/shell.py:538 +msgid "DEPRECATED! Only keystone is supported." +msgstr "" + +#: neutronclient/shell.py:547 +msgid "Defaults to env[OS_CLOUD]." +msgstr "" + +#: neutronclient/shell.py:552 +msgid "Authentication URL, defaults to env[OS_AUTH_URL]." +msgstr "" + +#: neutronclient/shell.py:561 +msgid "Authentication tenant name, defaults to env[OS_TENANT_NAME]." +msgstr "" + +#: neutronclient/shell.py:580 +msgid "Authentication tenant ID, defaults to env[OS_TENANT_ID]." +msgstr "" + +#: neutronclient/shell.py:594 +msgid "Authentication username, defaults to env[OS_USERNAME]." +msgstr "" + +#: neutronclient/shell.py:602 +msgid "Authentication user ID (Env: OS_USER_ID)" +msgstr "" + +#: neutronclient/shell.py:654 +msgid "" +"Path of certificate file to use in SSL connection. This file can " +"optionally be prepended with the private key. Defaults to env[OS_CERT]." +msgstr "" + +#: neutronclient/shell.py:663 +msgid "" +"Specify a CA bundle file to use in verifying a TLS (https) server " +"certificate. Defaults to env[OS_CACERT]." +msgstr "" + +#: neutronclient/shell.py:671 +msgid "" +"Path of client key to use in SSL connection. This option is not necessary" +" if your key is prepended to your certificate file. Defaults to " +"env[OS_KEY]." +msgstr "" + +#: neutronclient/shell.py:679 +msgid "Authentication password, defaults to env[OS_PASSWORD]." +msgstr "" + +#: neutronclient/shell.py:687 +msgid "Authentication region name, defaults to env[OS_REGION_NAME]." +msgstr "" + +#: neutronclient/shell.py:696 +msgid "Authentication token, defaults to env[OS_TOKEN]." +msgstr "" + +#: neutronclient/shell.py:704 +msgid "" +"Timeout in seconds to wait for an HTTP response. Defaults to " +"env[OS_NETWORK_TIMEOUT] or None if not specified." +msgstr "" + +#: neutronclient/shell.py:710 +msgid "Defaults to env[OS_URL]." +msgstr "" + +#: neutronclient/shell.py:719 +msgid "" +"Explicitly allow neutronclient to perform \"insecure\" SSL (https) " +"requests. The server's certificate will not be verified against any " +"certificate authorities. This option should be used with caution." +msgstr "" + +#: neutronclient/shell.py:821 +#, python-format +msgid "Try 'neutron help %s' for more information." +msgstr "" + +#: neutronclient/common/exceptions.py:39 +msgid "An unknown exception occurred." +msgstr "" + +#: neutronclient/common/exceptions.py:78 +msgid "Unauthorized: bad credentials." +msgstr "" + +#: neutronclient/common/exceptions.py:83 +msgid "Forbidden: your credentials don't give you access to this resource." +msgstr "" + +#: neutronclient/common/exceptions.py:170 +msgid "auth_url was not provided to the Neutron client" +msgstr "" + +#: neutronclient/common/exceptions.py:174 +msgid "Could not find Service or Region in Service Catalog." +msgstr "" + +#: neutronclient/common/exceptions.py:178 +#, python-format +msgid "Could not find endpoint type %(type_)s in Service Catalog." +msgstr "" + +#: neutronclient/common/exceptions.py:182 +msgid "" +"Found more than one matching endpoint in Service Catalog: " +"%(matching_endpoints)" +msgstr "" + +#: neutronclient/common/exceptions.py:195 +#, python-format +msgid "Connection to neutron failed: %(reason)s" +msgstr "" + +#: neutronclient/common/exceptions.py:199 +#, python-format +msgid "SSL certificate validation has failed: %(reason)s" +msgstr "" + +#: neutronclient/common/exceptions.py:203 +#, python-format +msgid "Malformed response body: %(reason)s" +msgstr "" + +#: neutronclient/common/exceptions.py:207 +#, python-format +msgid "Invalid content type %(content_type)s." +msgstr "" + +#: neutronclient/common/exceptions.py:229 +#, python-format +msgid "" +"Multiple %(resource)s matches found for name '%(name)s', use an ID to be " +"more specific." +msgstr "" + +#: neutronclient/common/serializer.py:223 +msgid "Cannot understand JSON" +msgstr "" + +#: neutronclient/common/serializer.py:296 +msgid "Cannot understand XML" +msgstr "" + +#: neutronclient/common/utils.py:56 +#, python-format +msgid "" +"Invalid %(api_name)s client version '%(version)s'. must be one of: " +"%(map_keys)s" +msgstr "" + +#: neutronclient/common/utils.py:113 +#, python-format +msgid "invalid key-value '%s', expected format: key=value" +msgstr "" + +#: neutronclient/common/validators.py:38 +#, python-format +msgid "%(attr_name)s \"%(val)s\" should be an integer [%(min)i:%(max)i]." +msgstr "" + +#: neutronclient/common/validators.py:43 +#, python-format +msgid "" +"%(attr_name)s \"%(val)s\" should be an integer greater than or equal to " +"%(min)i." +msgstr "" + +#: neutronclient/common/validators.py:48 +#, python-format +msgid "" +"%(attr_name)s \"%(val)s\" should be an integer smaller than or equal to " +"%(max)i." +msgstr "" + +#: neutronclient/common/validators.py:53 +#, python-format +msgid "%(attr_name)s \"%(val)s\" should be an integer." +msgstr "" + +#: neutronclient/common/validators.py:68 +#, python-format +msgid "%(attr_name)s \"%(val)s\" is not a valid CIDR." +msgstr "" + +#: neutronclient/neutron/client.py:56 +#, python-format +msgid "API version %s is not supported" +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:69 +#, python-format +msgid "Unable to find %(resource)s with id '%(id)s'" +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:106 +#, python-format +msgid "Unable to find %(resource)s with name '%(name)s'" +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:139 +msgid "Show detailed information." +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:154 +msgid "Specify the field(s) to be returned by server. You can repeat this option." +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:164 +msgid "" +"Specify retrieve unit of each request, then split one request to several " +"requests." +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:174 +msgid "" +"Sorts the list by the specified fields in the specified directions. You " +"can repeat this option, but you must specify an equal number of sort_dir " +"and sort_key values. Extra sort_dir options are ignored. Missing sort_dir" +" options use the default asc value." +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:183 +msgid "Sorts the list in the specified direction. You can repeat this option." +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:209 +#: neutronclient/neutron/v2_0/__init__.py:287 +#: neutronclient/neutron/v2_0/__init__.py:309 +#: neutronclient/neutron/v2_0/__init__.py:314 +#, python-format +msgid "Invalid values_specs %s" +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:279 +#, python-format +msgid "Duplicated options %s" +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:429 +msgid "The XML or JSON request format." +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:478 +#: neutronclient/neutron/v2_0/quota.py:47 +#: neutronclient/neutron/v2_0/quota.py:108 +#: neutronclient/neutron/v2_0/quota.py:152 +msgid "The owner tenant ID." +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:504 +#, python-format +msgid "Created a new %s:" +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:521 +#, python-format +msgid "ID or name of %s to update." +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:523 +#, python-format +msgid "ID of %s to update." +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:545 +#, python-format +msgid "Must specify new values to update %s" +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:561 +#, python-format +msgid "Updated %(resource)s: %(id)s" +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:577 +#, python-format +msgid "ID or name of %s to delete." +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:579 +#, python-format +msgid "ID of %s to delete." +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:607 +#, python-format +msgid "Deleted %(resource)s: %(id)s" +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:732 +#, python-format +msgid "ID or name of %s to look up." +msgstr "" + +#: neutronclient/neutron/v2_0/__init__.py:734 +#, python-format +msgid "ID of %s to look up." +msgstr "" + +#: neutronclient/neutron/v2_0/address_scope.py:45 +msgid "Set the address scope as shared." +msgstr "" + +#: neutronclient/neutron/v2_0/address_scope.py:48 +msgid "Specify the name of the address scope." +msgstr "" + +#: neutronclient/neutron/v2_0/address_scope.py:71 +msgid "Name of the address scope to update." +msgstr "" + +#: neutronclient/neutron/v2_0/agent.py:69 +msgid "Set admin state up of the agent to false." +msgstr "" + +#: neutronclient/neutron/v2_0/agent.py:72 +msgid "Description for the agent." +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:35 +#: neutronclient/neutron/v2_0/agentscheduler.py:60 +#: neutronclient/neutron/v2_0/agentscheduler.py:88 +msgid "ID of the DHCP agent." +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:38 +msgid "Network to add." +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:49 +#, python-format +msgid "Added network %s to DHCP agent" +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:63 +msgid "Network to remove." +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:74 +#, python-format +msgid "Removed network %s from DHCP agent" +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:110 +msgid "Network to query." +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:133 +#: neutronclient/neutron/v2_0/agentscheduler.py:158 +msgid "ID of the L3 agent." +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:136 +msgid "Router to add." +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:147 +#, python-format +msgid "Added router %s to L3 agent" +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:161 +msgid "Router to remove." +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:172 +#, python-format +msgid "Removed router %s from L3 agent" +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:190 +msgid "ID of the L3 agent to query." +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:211 +msgid "Router to query." +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:243 +#: neutronclient/neutron/v2_0/agentscheduler.py:296 +msgid "ID of the loadbalancer agent to query." +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:267 +msgid "Pool to query." +msgstr "" + +#: neutronclient/neutron/v2_0/agentscheduler.py:320 +msgid "LoadBalancer to query." +msgstr "" + +#: neutronclient/neutron/v2_0/floatingip.py:50 +msgid "Network name or ID to allocate floating IP from." +msgstr "" + +#: neutronclient/neutron/v2_0/floatingip.py:53 +msgid "ID of the port to be associated with the floating IP." +msgstr "" + +#: neutronclient/neutron/v2_0/floatingip.py:59 +#: neutronclient/neutron/v2_0/floatingip.py:107 +msgid "IP address on the port (only required if port has multiple IPs)." +msgstr "" + +#: neutronclient/neutron/v2_0/floatingip.py:66 +msgid "IP address of the floating IP" +msgstr "" + +#: neutronclient/neutron/v2_0/floatingip.py:70 +msgid "Subnet ID on which you want to create the floating IP." +msgstr "" + +#: neutronclient/neutron/v2_0/floatingip.py:100 +msgid "ID of the floating IP to associate." +msgstr "" + +#: neutronclient/neutron/v2_0/floatingip.py:103 +msgid "ID or name of the port to be associated with the floating IP." +msgstr "" + +#: neutronclient/neutron/v2_0/floatingip.py:123 +#, python-format +msgid "Associated floating IP %s" +msgstr "" + +#: neutronclient/neutron/v2_0/floatingip.py:138 +msgid "ID of the floating IP to disassociate." +msgstr "" + +#: neutronclient/neutron/v2_0/floatingip.py:147 +#, python-format +msgid "Disassociated floating IP %s" +msgstr "" + +#: neutronclient/neutron/v2_0/metering.py:43 +msgid "Name of metering label to create." +msgstr "" + +#: neutronclient/neutron/v2_0/metering.py:46 +msgid "Description of metering label to create." +msgstr "" + +#: neutronclient/neutron/v2_0/metering.py:50 +msgid "Set the label as shared." +msgstr "" + +#: neutronclient/neutron/v2_0/metering.py:89 +msgid "Id or Name of the label." +msgstr "" + +#: neutronclient/neutron/v2_0/metering.py:92 +#: neutronclient/neutron/v2_0/securitygroup.py:330 +msgid "CIDR to match on." +msgstr "" + +#: neutronclient/neutron/v2_0/metering.py:96 +msgid "Direction of traffic, default: ingress." +msgstr "" + +#: neutronclient/neutron/v2_0/metering.py:100 +msgid "Exclude this CIDR from the label, default: not excluded." +msgstr "" + +#: neutronclient/neutron/v2_0/network.py:114 +#: neutronclient/neutron/v2_0/port.py:230 +#: neutronclient/neutron/v2_0/router.py:62 +#: neutronclient/neutron/v2_0/fw/firewall.py:56 +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:48 +#: neutronclient/neutron/v2_0/lb/member.py:48 +#: neutronclient/neutron/v2_0/lb/pool.py:54 +#: neutronclient/neutron/v2_0/lb/vip.py:52 +#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:49 +#: neutronclient/neutron/v2_0/lb/v2/listener.py:55 +#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:51 +#: neutronclient/neutron/v2_0/lb/v2/pool.py:60 +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:98 +#: neutronclient/neutron/v2_0/vpn/vpnservice.py:47 +msgid "Set admin state up to false." +msgstr "" + +#: neutronclient/neutron/v2_0/network.py:122 +msgid "Set the network as shared." +msgstr "" + +#: neutronclient/neutron/v2_0/network.py:127 +msgid "The physical mechanism by which the virtual network is implemented." +msgstr "" + +#: neutronclient/neutron/v2_0/network.py:132 +msgid "" +"Name of the physical network over which the virtual network is " +"implemented." +msgstr "" + +#: neutronclient/neutron/v2_0/network.py:137 +msgid "VLAN ID for VLAN networks or tunnel-id for GRE/VXLAN networks." +msgstr "" + +#: neutronclient/neutron/v2_0/network.py:143 +msgid "Create a vlan transparent network." +msgstr "" + +#: neutronclient/neutron/v2_0/network.py:146 +msgid "Name of network to create." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:45 +msgid "Name of this port." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:49 +msgid "" +"Desired IP and/or subnet for this port: " +"subnet_id=,ip_address=. You can repeat this option." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:58 +msgid "Device ID of this port." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:64 +msgid "Device owner of this port." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:111 +msgid "ID or name of router to look up." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:135 +msgid "Security group associated with the port. You can repeat this option." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:140 +msgid "Associate no security groups with the port." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:162 +msgid "" +"Extra dhcp options to be assigned to this port: " +"opt_name=,opt_value=,ip_version={4,6}. You can " +"repeat this option." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:173 +msgid "" +"Invalid --extra-dhcp-opt option, can only be: " +"opt_name=,opt_value=,ip_version={4,6}. You can " +"repeat this option." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:204 +msgid "Allowed address pair associated with the port.You can repeat this option." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:209 +msgid "Associate no allowed address pairs with the port." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:237 +msgid "MAC address of this port." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:244 +msgid "VNIC type for this port." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:251 +msgid "Custom data to be passed as binding:profile." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:262 +msgid "Network ID or name this port belongs to." +msgstr "" + +#: neutronclient/neutron/v2_0/port.py:305 +msgid "Set admin state up for the port." +msgstr "" + +#: neutronclient/neutron/v2_0/quota.py:62 +#, python-format +msgid "Deleted %(resource)s: %(tenant_id)s" +msgstr "" + +#: neutronclient/neutron/v2_0/quota.py:158 +msgid "The limit of networks." +msgstr "" + +#: neutronclient/neutron/v2_0/quota.py:161 +msgid "The limit of subnets." +msgstr "" + +#: neutronclient/neutron/v2_0/quota.py:164 +msgid "The limit of ports." +msgstr "" + +#: neutronclient/neutron/v2_0/quota.py:167 +msgid "The limit of routers." +msgstr "" + +#: neutronclient/neutron/v2_0/quota.py:170 +msgid "The limit of floating IPs." +msgstr "" + +#: neutronclient/neutron/v2_0/quota.py:173 +msgid "The limit of security groups." +msgstr "" + +#: neutronclient/neutron/v2_0/quota.py:176 +msgid "The limit of security groups rules." +msgstr "" + +#: neutronclient/neutron/v2_0/quota.py:179 +msgid "The limit of vips." +msgstr "" + +#: neutronclient/neutron/v2_0/quota.py:182 +msgid "The limit of pools." +msgstr "" + +#: neutronclient/neutron/v2_0/quota.py:185 +msgid "The limit of pool members." +msgstr "" + +#: neutronclient/neutron/v2_0/quota.py:188 +msgid "The limit of health monitors." +msgstr "" + +#: neutronclient/neutron/v2_0/quota.py:196 +#, python-format +msgid "Quota limit for %(name)s must be an integer" +msgstr "" + +#: neutronclient/neutron/v2_0/rbac.py:54 +msgid "ID or name of the RBAC object." +msgstr "" + +#: neutronclient/neutron/v2_0/rbac.py:58 +msgid "Type of the object that RBAC policy affects." +msgstr "" + +#: neutronclient/neutron/v2_0/rbac.py:61 neutronclient/neutron/v2_0/rbac.py:91 +msgid "ID of the tenant to which the RBAC policy will be enforced." +msgstr "" + +#: neutronclient/neutron/v2_0/rbac.py:66 +msgid "Action for the RBAC policy." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:69 +msgid "Name of router to create." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:72 +msgid "Create a distributed router." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:75 +msgid "Create a highly available router." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:98 +msgid "Name of this router." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:101 +msgid "Specify the administrative state of the router (True meaning \"Up\")" +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:108 +msgid "True means this router should operate in distributed mode." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:114 +msgid "Route to associate with the router. You can repeat this option." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:119 +msgid "Remove routes associated with the router." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:150 +#: neutronclient/neutron/v2_0/router.py:218 +#: neutronclient/neutron/v2_0/router.py:269 +msgid "ID or name of the router." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:153 +msgid "" +"The format is \"SUBNET|subnet=SUBNET|port=PORT\". Either a subnet or port" +" must be specified. Both ID and name are accepted as SUBNET or PORT. Note" +" that \"subnet=\" can be omitted when specifying a subnet." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:168 +msgid "You must specify either subnet or port for INTERFACE parameter." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:193 +#, python-format +msgid "Added interface %(port)s to router %(router)s." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:205 +#, python-format +msgid "Removed interface from router %s." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:221 +msgid "ID or name of the external network for the gateway." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:224 +msgid "Disable source NAT on the router gateway." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:227 +msgid "" +"Desired IP and/or subnet on external network: " +"subnet_id=,ip_address=. You can repeat this option." +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:255 +#, python-format +msgid "Set gateway for router %s" +msgstr "" + +#: neutronclient/neutron/v2_0/router.py:279 +#, python-format +msgid "Removed gateway from router %s" +msgstr "" + +#: neutronclient/neutron/v2_0/securitygroup.py:126 +#: neutronclient/neutron/v2_0/securitygroup.py:153 +msgid "Name of security group." +msgstr "" + +#: neutronclient/neutron/v2_0/securitygroup.py:129 +#: neutronclient/neutron/v2_0/securitygroup.py:156 +msgid "Description of security group." +msgstr "" + +#: neutronclient/neutron/v2_0/securitygroup.py:189 +msgid "Do not convert security group ID to its name." +msgstr "" + +#: neutronclient/neutron/v2_0/securitygroup.py:305 +msgid "Security group name or ID to add rule." +msgstr "" + +#: neutronclient/neutron/v2_0/securitygroup.py:309 +msgid "Direction of traffic: ingress/egress." +msgstr "" + +#: neutronclient/neutron/v2_0/securitygroup.py:312 +msgid "IPv4/IPv6" +msgstr "" + +#: neutronclient/neutron/v2_0/securitygroup.py:315 +msgid "Protocol of packet." +msgstr "" + +#: neutronclient/neutron/v2_0/securitygroup.py:318 +msgid "Starting port range." +msgstr "" + +#: neutronclient/neutron/v2_0/securitygroup.py:324 +msgid "Ending port range." +msgstr "" + +#: neutronclient/neutron/v2_0/securitygroup.py:336 +msgid "Remote security group name or ID to apply rule." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:54 +msgid "Name of this subnet." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:58 +msgid "Gateway IP of this subnet." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:62 +msgid "No distribution of gateway." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:66 +msgid "" +"Allocation pool IP addresses for this subnet (This option can be " +"repeated)." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:75 +msgid "Additional route (This option can be repeated)." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:79 +msgid "DNS name server for this subnet (This option can be repeated)." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:84 +msgid "Disable DHCP for this subnet." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:88 +msgid "Enable DHCP for this subnet." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:107 +msgid "You cannot enable and disable DHCP at the same time." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:123 +msgid "--ipv6-ra-mode is invalid when --ip-version is 4" +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:128 +msgid "--ipv6-address-mode is invalid when --ip-version is 4" +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:163 +msgid "" +"IP version to use, default is 4. Note that when subnetpool is specified, " +"IP version is determined from the subnetpool and this option is ignored." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:174 +msgid "Network ID or name this subnet belongs to." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:177 +msgid "CIDR of subnet to create." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:181 +msgid "IPv6 RA (Router Advertisement) mode." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:185 +msgid "IPv6 address mode." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:188 +msgid "ID or name of subnetpool from which this subnet will obtain a CIDR." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:192 +msgid "Prefix length for subnet allocation from subnetpool." +msgstr "" + +#: neutronclient/neutron/v2_0/subnet.py:224 +#, python-format +msgid "" +"An IPv%(ip)d subnet with a %(cidr)s CIDR will have only one usable IP " +"address so the device attached to it will not have any IP connectivity." +msgstr "" + +#: neutronclient/neutron/v2_0/subnetpool.py:24 +msgid "Subnetpool minimum prefix length." +msgstr "" + +#: neutronclient/neutron/v2_0/subnetpool.py:27 +msgid "Subnetpool maximum prefix length." +msgstr "" + +#: neutronclient/neutron/v2_0/subnetpool.py:30 +msgid "Subnetpool default prefix length." +msgstr "" + +#: neutronclient/neutron/v2_0/subnetpool.py:34 +msgid "Subnetpool prefixes (This option can be repeated)." +msgstr "" + +#: neutronclient/neutron/v2_0/subnetpool.py:69 +msgid "Set the subnetpool as shared." +msgstr "" + +#: neutronclient/neutron/v2_0/subnetpool.py:72 +msgid "Name of subnetpool to create." +msgstr "" + +#: neutronclient/neutron/v2_0/subnetpool.py:76 +#: neutronclient/neutron/v2_0/subnetpool.py:113 +msgid "" +"ID or name of the address scope with which the subnetpool is associated. " +"Prefixes must be unique across address scopes" +msgstr "" + +#: neutronclient/neutron/v2_0/subnetpool.py:109 +msgid "Name of subnetpool to update." +msgstr "" + +#: neutronclient/neutron/v2_0/subnetpool.py:119 +msgid "Detach subnetpool from the address scope" +msgstr "" + +#: neutronclient/neutron/v2_0/contrib/_fox_sockets.py:24 +#: neutronclient/neutron/v2_0/contrib/_fox_sockets.py:77 +msgid "Name of this fox socket." +msgstr "" + +#: neutronclient/neutron/v2_0/flavor/flavor.py:49 +#: neutronclient/neutron/v2_0/flavor/flavor.py:86 +msgid "Name for the flavor." +msgstr "" + +#: neutronclient/neutron/v2_0/flavor/flavor.py:53 +msgid "" +"Service type to which the flavor applies to: e.g. VPN. (See service-" +"provider-list for loaded examples.)" +msgstr "" + +#: neutronclient/neutron/v2_0/flavor/flavor.py:57 +#: neutronclient/neutron/v2_0/flavor/flavor.py:89 +msgid "Description for the flavor." +msgstr "" + +#: neutronclient/neutron/v2_0/flavor/flavor.py:62 +#: neutronclient/neutron/v2_0/flavor/flavor.py:94 +#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:57 +#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:92 +msgid "Sets enabled flag." +msgstr "" + +#: neutronclient/neutron/v2_0/flavor/flavor.py:113 +msgid "Name or ID of the flavor to associate." +msgstr "" + +#: neutronclient/neutron/v2_0/flavor/flavor.py:117 +msgid "ID of the flavor profile to be associated with the flavor." +msgstr "" + +#: neutronclient/neutron/v2_0/flavor/flavor.py:130 +#, python-format +msgid "Associated flavor %(flavor)s with flavor_profile %(profile)s" +msgstr "" + +#: neutronclient/neutron/v2_0/flavor/flavor.py:147 +msgid "Name or ID of the flavor." +msgstr "" + +#: neutronclient/neutron/v2_0/flavor/flavor.py:151 +msgid "ID of the flavor profile to be disassociated from the flavor." +msgstr "" + +#: neutronclient/neutron/v2_0/flavor/flavor.py:163 +#, python-format +msgid "Disassociated flavor %(flavor)s from flavor_profile %(profile)s" +msgstr "" + +#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:46 +#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:81 +msgid "Description for the flavor profile." +msgstr "" + +#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:49 +#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:84 +msgid "Python module path to driver." +msgstr "" + +#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:52 +#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:87 +msgid "Metainfo for the flavor profile." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewall.py:45 +#: neutronclient/neutron/v2_0/fw/firewall.py:89 +msgid "Firewall policy name or ID." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewall.py:48 +msgid "Name for the firewall." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewall.py:51 +#: neutronclient/neutron/v2_0/fw/firewallrule.py:77 +msgid "Description for the firewall rule." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewall.py:62 +#: neutronclient/neutron/v2_0/fw/firewall.py:96 +msgid "" +"Firewall associated router names or IDs (requires FWaaS router insertion " +"extension, this option can be repeated)" +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewall.py:101 +msgid "" +"Associate no routers with the firewall (requires FWaaS router insertion " +"extension)" +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:37 +msgid "" +"Ordered list of whitespace-delimited firewall rule names or IDs; e.g., " +"--firewall-rules \"rule1 rule2\"" +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:83 +msgid "Name for the firewall policy." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:86 +msgid "Description for the firewall policy." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:91 +msgid "Create a shared policy." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:97 +msgid "Sets audited to True." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:159 +msgid "Insert before this rule." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:163 +msgid "Insert after this rule." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:167 +msgid "New rule to insert." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:179 +#, python-format +msgid "Inserted firewall rule in firewall policy %(id)s" +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:206 +msgid "Firewall rule to remove from policy." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:218 +#, python-format +msgid "Removed firewall rule from firewall policy %(id)s" +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallrule.py:74 +msgid "Name for the firewall rule." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallrule.py:82 +msgid "Set shared to True (default is False)." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallrule.py:86 +msgid "Source IP address or subnet." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallrule.py:89 +msgid "Destination IP address or subnet." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallrule.py:92 +msgid "Source port (integer in [1, 65535] or range in a:b)." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallrule.py:95 +msgid "Destination port (integer in [1, 65535] or range in a:b)." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallrule.py:99 +msgid "Whether to enable or disable this rule." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallrule.py:103 +#: neutronclient/neutron/v2_0/fw/firewallrule.py:133 +msgid "Protocol for the firewall rule." +msgstr "" + +#: neutronclient/neutron/v2_0/fw/firewallrule.py:108 +msgid "Action for the firewall rule." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:51 +#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:52 +msgid "" +"The list of HTTP status codes expected in response from the member to " +"declare it healthy. This attribute can contain one value, or a list of " +"values separated by comma, or a range of values (e.g. \"200-299\"). If " +"this attribute is not specified, it defaults to \"200\"." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:59 +#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:60 +msgid "The HTTP method used for requests by the monitor of type HTTP." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:63 +#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:64 +msgid "" +"The HTTP path used in the HTTP request used by the monitor to test a " +"member health. This must be a string beginning with a / (forward slash)." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:69 +#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:70 +msgid "The time in seconds between sending probes to members." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:73 +msgid "" +"Number of permissible connection failures before changing the member " +"status to INACTIVE. [1..10]" +msgstr "" + +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:78 +#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:79 +msgid "" +"Maximum number of seconds for a monitor to wait for a connection to be " +"established before it times out. The value must be less than the delay " +"value." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:84 +#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:85 +msgid "One of the predefined health monitor types." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:121 +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:148 +msgid "Health monitor to associate." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:124 +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:151 +msgid "ID of the pool to be associated with the health monitor." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:134 +#, python-format +msgid "Associated health monitor %s" +msgstr "" + +#: neutronclient/neutron/v2_0/lb/healthmonitor.py:162 +#, python-format +msgid "Disassociated health monitor %s" +msgstr "" + +#: neutronclient/neutron/v2_0/lb/member.py:51 +msgid "Weight of pool member in the pool (default:1, [0..256])." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/member.py:55 +msgid "IP address of the pool member on the pool network." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/member.py:59 +#: neutronclient/neutron/v2_0/lb/v2/member.py:90 +msgid "Port on which the pool member listens for requests or connections." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/member.py:63 +#: neutronclient/neutron/v2_0/lb/vip.py:45 +msgid "Pool ID or name this vip belongs to." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/pool.py:57 +#: neutronclient/neutron/v2_0/lb/v2/pool.py:63 +msgid "Description of the pool." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/pool.py:62 +#: neutronclient/neutron/v2_0/lb/v2/pool.py:75 +msgid "The algorithm used to distribute load between the members of the pool." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/pool.py:67 +#: neutronclient/neutron/v2_0/lb/v2/pool.py:70 +msgid "The name of the pool." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/pool.py:72 +#: neutronclient/neutron/v2_0/lb/vip.py:72 +#: neutronclient/neutron/v2_0/lb/v2/pool.py:85 +msgid "Protocol for balancing." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/pool.py:76 +msgid "The subnet on which the members of the pool will be located." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/pool.py:80 +msgid "Provider name of loadbalancer service." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/vip.py:48 +msgid "IP address of the vip." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/vip.py:55 +#: neutronclient/neutron/v2_0/lb/v2/listener.py:58 +msgid "" +"The maximum number of connections per second allowed for the vip. " +"Positive integer or -1 for unlimited (default)." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/vip.py:59 +msgid "Description of the vip." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/vip.py:63 +msgid "Name of the vip." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/vip.py:67 +msgid "" +"TCP port on which to listen for client traffic that is associated with " +"the vip address." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/vip.py:76 +msgid "The subnet on which to allocate the vip address." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:74 +msgid "" +"Number of permissible connection failures before changing the member " +"status to INACTIVE. [1..10]." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:88 +msgid "ID or name of the pool that this healthmonitor will monitor." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/listener.py:62 +msgid "Description of the listener." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/listener.py:65 +msgid "The name of the listener." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/listener.py:69 +msgid "Default TLS container reference to retrieve TLS information." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/listener.py:75 +msgid "List of TLS container references for SNI." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/listener.py:80 +msgid "ID or name of the load balancer." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/listener.py:85 +msgid "Protocol for the listener." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/listener.py:90 +msgid "Protocol port for the listener." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:47 +msgid "Description of the load balancer." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:54 +msgid "Name of the load balancer." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:57 +msgid "Provider name of load balancer service." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:60 +msgid "ID or name of flavor." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:63 +msgid "VIP address for the load balancer." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:66 +msgid "Load balancer VIP subnet." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/member.py:37 +#: neutronclient/neutron/v2_0/lb/v2/member.py:94 +msgid "ID or name of the pool that this member belongs to." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/member.py:75 +#: neutronclient/neutron/v2_0/lb/v2/member.py:119 +msgid "Set admin state up to false" +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/member.py:78 +msgid "Weight of member in the pool (default:1, [0..256])." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/member.py:82 +msgid "Subnet ID or name for the member." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/member.py:86 +msgid "IP address of the pool member in the pool." +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/member.py:122 +msgid "Weight of member in the pool (default:1, [0..256])" +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/member.py:125 +msgid "ID or name of the pool that this member belongs to" +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/pool.py:67 +msgid "The type of session persistence to use and associated cookie name" +msgstr "" + +#: neutronclient/neutron/v2_0/lb/v2/pool.py:80 +msgid "The listener to associate with the pool" +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/networkgateway.py:25 +msgid "" +"Type of the transport zone connector to use for this device. Valid values" +" are gre, stt, ipsec_gre, ipsec_stt, and bridge. Defaults to stt. " +"ipsecgre and ipsecstt are also accepted as alternative names" +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/networkgateway.py:29 +msgid "" +"IP address for this device's transport connector. It must correspond to " +"the IP address of the interface used for tenant traffic on the NSX " +"gateway node." +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/networkgateway.py:32 +msgid "" +"PEM certificate used by the NSX gateway transport node to authenticate " +"with the NSX controller." +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/networkgateway.py:34 +msgid "" +"File containing the PEM certificate used by the NSX gateway transport " +"node to authenticate with the NSX controller." +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/networkgateway.py:170 +msgid "Name of network gateway to create." +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/networkgateway.py:174 +msgid "" +"Device info for this gateway. You can repeat this option for multiple " +"devices for HA gateways." +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/networkgateway.py:212 +msgid "ID of the network gateway." +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/networkgateway.py:215 +msgid "ID of the internal network to connect on the gateway." +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/networkgateway.py:218 +msgid "" +"L2 segmentation strategy on the external side of the gateway (e.g.: VLAN," +" FLAT)." +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/networkgateway.py:222 +msgid "Identifier for the L2 segment on the external side of the gateway." +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/networkgateway.py:249 +#, python-format +msgid "Connected network to gateway %s" +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/networkgateway.py:268 +#, python-format +msgid "Disconnected network from gateway %s" +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/qos_queue.py:43 +msgid "Name of queue." +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/qos_queue.py:46 +msgid "Minimum rate." +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/qos_queue.py:49 +msgid "Maximum rate." +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/qos_queue.py:52 +msgid "QOS marking as untrusted or trusted." +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/qos_queue.py:56 +msgid "" +"If true all created ports will be the size of this queue, if queue is not" +" specified" +msgstr "" + +#: neutronclient/neutron/v2_0/nsx/qos_queue.py:60 +msgid "Differentiated Services Code Point." +msgstr "" + +#: neutronclient/neutron/v2_0/qos/bandwidth_limit_rule.py:30 +msgid "max bandwidth in kbps." +msgstr "" + +#: neutronclient/neutron/v2_0/qos/bandwidth_limit_rule.py:33 +msgid "max burst bandwidth in kbps." +msgstr "" + +#: neutronclient/neutron/v2_0/qos/bandwidth_limit_rule.py:40 +msgid "Must provide max_kbps or max_burst_kbps option." +msgstr "" + +#: neutronclient/neutron/v2_0/qos/policy.py:34 +msgid "Attach QoS policy ID or name to the resource." +msgstr "" + +#: neutronclient/neutron/v2_0/qos/policy.py:51 +msgid "Detach QoS policy from the resource." +msgstr "" + +#: neutronclient/neutron/v2_0/qos/policy.py:95 +msgid "Name of QoS policy to create." +msgstr "" + +#: neutronclient/neutron/v2_0/qos/policy.py:98 +#: neutronclient/neutron/v2_0/qos/policy.py:128 +msgid "Description of the QoS policy." +msgstr "" + +#: neutronclient/neutron/v2_0/qos/policy.py:102 +#: neutronclient/neutron/v2_0/qos/policy.py:132 +msgid "Accessible by other tenants. Set shared to True (default is False)." +msgstr "" + +#: neutronclient/neutron/v2_0/qos/policy.py:125 +msgid "Name of QoS policy." +msgstr "" + +#: neutronclient/neutron/v2_0/qos/rule.py:26 +msgid "ID or name of the QoS policy." +msgstr "" + +#: neutronclient/neutron/v2_0/qos/rule.py:32 +msgid "ID of the QoS rule." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/endpoint_group.py:24 +msgid "Set a name for the endpoint group." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/endpoint_group.py:27 +msgid "Set a description for the endpoint group." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/endpoint_group.py:32 +msgid "Type of endpoints in group (e.g. subnet, cidr, vlan)." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/endpoint_group.py:37 +msgid "Endpoint(s) for the group. Must all be of the same type." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:48 +msgid "Description of the IKE policy" +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:52 +msgid "Authentication algorithm in lowercase. Default:sha1" +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:57 +#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:60 +msgid "Encryption algorithm in lowercase, default:aes-128" +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:61 +msgid "IKE Phase1 negotiation mode in lowercase, default:main" +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:65 +msgid "IKE version in lowercase, default:v1" +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:69 +#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:68 +msgid "Perfect Forward Secrecy in lowercase, default:group5" +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:77 +msgid "Name of the IKE policy." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:61 +msgid "Local endpoint group ID/name with subnet(s) for IPSec connection." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:65 +msgid "Peer endpoint group ID/name with CIDR(s) for IPsec connection." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:101 +msgid "Set friendly name for the connection." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:104 +msgid "Set a description for the connection." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:108 +msgid "MTU size for the connection, default:1500" +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:113 +msgid "Initiator state in lowercase, default:bi-directional" +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:117 +msgid "VPN service instance ID associated with this connection." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:121 +msgid "IKE policy ID associated with this connection." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:125 +msgid "IPsec policy ID associated with this connection." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:129 +msgid "Peer gateway public IPv4/IPv6 address or FQDN." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:133 +msgid "" +"Peer router identity for authentication. Can be IPv4/IPv6 address, e-mail" +" address, key id, or FQDN." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:138 +msgid "" +"[DEPRECATED in Mitaka] Remote subnet(s) in CIDR format. Cannot be " +"specified when using endpoint groups. Only applicable, if subnet provided" +" for VPN service." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:144 +msgid "Pre-shared key string." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:158 +msgid "Invalid MTU value: MTU must be greater than or equal to 68" +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:163 +msgid "You must specify both local and peer endpoint groups." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:167 +msgid "You cannot specify both endpoint groups and peer CIDR(s)." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:171 +msgid "You must specify endpoint groups or peer CIDR(s)." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:48 +msgid "Description of the IPsec policy." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:52 +msgid "Transform protocol in lowercase, default:esp" +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:56 +msgid "Authentication algorithm in lowercase, default:sha1" +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:64 +msgid "Encapsulation mode in lowercase, default:tunnel" +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:76 +msgid "Name of the IPsec policy." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/utils.py:35 +#, python-format +msgid "" +"DPD Dictionary KeyError: Reason-Invalid DPD key : '%(key)s' not in " +"%(supported_key)s " +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/utils.py:42 +#, python-format +msgid "" +"DPD Dictionary ValueError: Reason-Invalid DPD action : '%(key_value)s' " +"not in %(supported_action)s " +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/utils.py:54 +#, python-format +msgid "" +"DPD Dictionary ValueError: Reason-Invalid positive integer value: " +"'%(key)s' = %(value)s " +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/utils.py:69 +#, python-format +msgid "" +"Lifetime Dictionary KeyError: Reason-Invalid unit key : '%(key)s' not in " +"%(supported_key)s " +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/utils.py:76 +#, python-format +msgid "" +"Lifetime Dictionary ValueError: Reason-Invalid units : '%(key_value)s' " +"not in %(supported_units)s " +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/utils.py:87 +#, python-format +msgid "" +"Lifetime Dictionary ValueError: Reason-Invalid value should be at least " +"60:'%(key_value)s' = %(value)s " +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/utils.py:99 +#, python-format +msgid "" +"%s lifetime attributes. 'units'-seconds, default:seconds. 'value'-non " +"negative integer, default:3600." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/utils.py:106 +#, python-format +msgid "" +" %s Dead Peer Detection attributes. 'action'-hold,clear,disabled,restart" +",restart-by-peer. 'interval' and 'timeout' are non negative integers. " +"'interval' should be less than 'timeout' value. 'action', default:hold " +"'interval', default:30, 'timeout', default:120." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/vpnservice.py:50 +msgid "Set a name for the VPN service." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/vpnservice.py:53 +msgid "Set a description for the VPN service." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/vpnservice.py:56 +msgid "Router unique identifier for the VPN service." +msgstr "" + +#: neutronclient/neutron/v2_0/vpn/vpnservice.py:59 +msgid "[DEPRECATED in Mitaka] Unique identifier for the local private subnet." +msgstr "" + +#: neutronclient/v2_0/client.py:228 +#, python-format +msgid "Unable to serialize object of type = '%s'" +msgstr "" + +#: neutronclient/v2_0/client.py:280 +#, python-format +msgid "Failed to connect to Neutron server after %d attempts" +msgstr "" + +#: neutronclient/v2_0/client.py:283 +msgid "Failed to connect Neutron server" +msgstr "" + diff --git a/setup.cfg b/setup.cfg index 0962bd0..7f11923 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,3 +39,17 @@ source-dir = doc/source [wheel] universal = 1 + +[extract_messages] +keywords = _ gettext ngettext l_ lazy_gettext +mapping_file = babel.cfg +output_file = python-neutronclient/locale/python-neutronclient.pot + +[compile_catalog] +directory = python-neutronclient/locale +domain = python-neutronclient + +[update_catalog] +domain = python-neutronclient +output_dir = python-neutronclient/locale +input_file = python-neutronclient/locale/python-neutronclient.pot From 96eff78ce9785956b95f22cf5c01b30e3de32814 Mon Sep 17 00:00:00 2001 From: Kenji Yasui Date: Tue, 15 Sep 2015 00:13:17 +0000 Subject: [PATCH 02/57] Add help information of 'firewall-rule-create' This patch adds information about ip-version to firewall-rule-create's help output. Change-Id: I799cdb98e12c90fb83965ca32e8fa21fc925856b Closes-Bug: #1495427 --- neutronclient/neutron/v2_0/fw/firewallrule.py | 7 ++- .../tests/unit/fw/test_cli20_firewallrule.py | 47 ++++++++++++++----- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/neutronclient/neutron/v2_0/fw/firewallrule.py b/neutronclient/neutron/v2_0/fw/firewallrule.py index 1f05363..97df1df 100644 --- a/neutronclient/neutron/v2_0/fw/firewallrule.py +++ b/neutronclient/neutron/v2_0/fw/firewallrule.py @@ -81,6 +81,10 @@ class CreateFirewallRule(neutronv20.CreateCommand): action='store_true', help=_('Set shared to True (default is False).'), default=argparse.SUPPRESS) + parser.add_argument( + '--ip-version', + type=int, choices=[4, 6], default=4, + help=_('IP version for the firewall rule (default is 4).')) parser.add_argument( '--source-ip-address', help=_('Source IP address or subnet.')) @@ -113,7 +117,8 @@ class CreateFirewallRule(neutronv20.CreateCommand): ['name', 'description', 'shared', 'protocol', 'source_ip_address', 'destination_ip_address', 'source_port', 'destination_port', - 'action', 'enabled', 'tenant_id']) + 'action', 'enabled', 'tenant_id', + 'ip_version']) protocol = parsed_args.protocol if protocol == 'any': protocol = None diff --git a/neutronclient/tests/unit/fw/test_cli20_firewallrule.py b/neutronclient/tests/unit/fw/test_cli20_firewallrule.py index 10290b6..9c22035 100644 --- a/neutronclient/tests/unit/fw/test_cli20_firewallrule.py +++ b/neutronclient/tests/unit/fw/test_cli20_firewallrule.py @@ -32,6 +32,7 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base): my_id = 'myid' protocol = 'tcp' action = 'allow' + ip_version = 4 args = ['--tenant-id', tenant_id, '--admin-state-up', '--protocol', protocol, @@ -42,7 +43,8 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base): self._test_create_resource(resource, cmd, name, my_id, args, position_names, position_values, protocol=protocol, action=action, - enabled=enabled, tenant_id=tenant_id) + enabled=enabled, tenant_id=tenant_id, + ip_version=ip_version) def test_create_enabled_firewall_rule_with_mandatory_params_lcase(self): self._test_create_firewall_rule_with_mandatory_params(enabled='true') @@ -56,7 +58,8 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base): def test_create_disabled_firewall_rule_with_mandatory_params(self): self._test_create_firewall_rule_with_mandatory_params(enabled='False') - def _setup_create_firewall_rule_with_all_params(self, protocol='tcp'): + def _setup_create_firewall_rule_with_all_params(self, protocol='tcp', + ip_version='4'): # firewall-rule-create with all params set. resource = 'firewall_rule' cmd = firewallrule.CreateFirewallRule(test_cli20.MyApp(sys.stdout), @@ -74,6 +77,7 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base): args = ['--description', description, '--shared', '--protocol', protocol, + '--ip-version', ip_version, '--source-ip-address', source_ip, '--destination-ip-address', destination_ip, '--source-port', source_port, @@ -86,16 +90,29 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base): position_values = [] if protocol == 'any': protocol = None - self._test_create_resource(resource, cmd, name, my_id, args, - position_names, position_values, - description=description, shared=True, - protocol=protocol, - source_ip_address=source_ip, - destination_ip_address=destination_ip, - source_port=source_port, - destination_port=destination_port, - action=action, enabled='True', - tenant_id=tenant_id) + if ip_version == '4' or ip_version == '6': + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values, + description=description, shared=True, + protocol=protocol, + ip_version=int(ip_version), + source_ip_address=source_ip, + destination_ip_address=destination_ip, + source_port=source_port, + destination_port=destination_port, + action=action, enabled='True', + tenant_id=tenant_id) + else: + self.assertRaises(SystemExit, self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values, + ip_version=int(ip_version), + source_ip_address=source_ip, + destination_ip_address=destination_ip, + source_port=source_port, + destination_port=destination_port, + action=action, enabled='True', + tenant_id=tenant_id) def test_create_firewall_rule_with_all_params(self): self._setup_create_firewall_rule_with_all_params() @@ -103,6 +120,12 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base): def test_create_firewall_rule_with_proto_any(self): self._setup_create_firewall_rule_with_all_params(protocol='any') + def test_create_firewall_rule_with_IP_version_6(self): + self._setup_create_firewall_rule_with_all_params(ip_version='6') + + def test_create_firewall_rule_with_invalid_IP_version(self): + self._setup_create_firewall_rule_with_all_params(ip_version='5') + def test_list_firewall_rules(self): # firewall-rule-list. resources = "firewall_rules" From 83d61566cee8b8e786b46103cf3e72fbe8077c1a Mon Sep 17 00:00:00 2001 From: shu-mutou Date: Wed, 2 Dec 2015 12:19:19 +0900 Subject: [PATCH 03/57] Delete python bytecode before every test run Because python creates pyc|pyo files and __pycache__ directories during tox runs, certain changes in the tree, like deletes of files, or switching branches, can create spurious errors. The target bytecodes for deletion are in normal directories, but not in dot started directory. Change-Id: Iee36c67d2e5e4d9e78b1b1b493030761e0f46e3c Closes-Bug: #1368661 --- tox.ini | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b6c0bd8..ded914c 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,14 @@ usedevelop = True install_command = pip install -U {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt -commands = python setup.py testr --testr-args='{posargs}' +# Delete bytecodes from normal directories before running tests. +# Note that bytecodes in dot directories will not be deleted +# to keep bytecodes of python modules installed into virtualenvs. +commands = sh -c "find . -type d -name '.?*' -prune -o \ + \( -type d -name '__pycache__' -o -type f -name '*.py[co]' \) \ + -print0 | xargs -0 rm -rf" + python setup.py testr --testr-args='{posargs}' +whitelist_externals = sh [testenv:pep8] commands = flake8 From 597800f5033d8e78c5bec102e1585339348f9ae4 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Thu, 31 Dec 2015 06:57:50 +0900 Subject: [PATCH 04/57] Support pagination listing in client extension Change-Id: Ibe4b803f3bcac2d13a9fe5992b2f7b665801ef3f Closes-Bug: #1530211 --- neutronclient/tests/unit/test_client_extension.py | 5 +++++ neutronclient/v2_0/client.py | 14 ++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/neutronclient/tests/unit/test_client_extension.py b/neutronclient/tests/unit/test_client_extension.py index d0609b3..0fccd5e 100644 --- a/neutronclient/tests/unit/test_client_extension.py +++ b/neutronclient/tests/unit/test_client_extension.py @@ -104,6 +104,11 @@ class CLITestV20ExtensionJSON(test_cli20.CLITestV20Base): cmd = fox_sockets.FoxInSocketsList(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_fox_pagination(self): + resources = 'fox_sockets' + cmd = fox_sockets.FoxInSocketsList(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + def test_show_fox_socket(self): # Show fox_socket: --fields id --fields name myid. resource = 'fox_socket' diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index 324fd12..10ff186 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -447,9 +447,9 @@ class Client(ClientBase): } @APIParamsCall - def list_ext(self, path, **_params): + def list_ext(self, collection, path, retrieve_all, **_params): """Client extension hook for lists.""" - return self.get(path, params=_params) + return self.list(collection, path, retrieve_all, **_params) @APIParamsCall def show_ext(self, path, id, **_params): @@ -1694,11 +1694,13 @@ class Client(ClientBase): setattr(self, "show_%s" % resource_singular, fn) def extend_list(self, resource_plural, path, parent_resource): - def _fx(**_params): - return self.list_ext(path, **_params) + def _fx(retrieve_all=True, **_params): + return self.list_ext(resource_plural, path, + retrieve_all, **_params) - def _parent_fx(parent_id, **_params): - return self.list_ext(path % parent_id, **_params) + def _parent_fx(parent_id, retrieve_all=True, **_params): + return self.list_ext(resource_plural, path % parent_id, + retrieve_all, **_params) fn = _fx if not parent_resource else _parent_fx setattr(self, "list_%s" % resource_plural, fn) From 918a21a0e086d7bbdd29b1b88763e029a4f3626d Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Sat, 2 Jan 2016 22:17:59 +0900 Subject: [PATCH 05/57] Drop unused TableFormater code TableFormater was introduced as a workaround and it was commented out in a later fix. It is no longer needed now. TrivialFix Change-Id: Ia960ee1abc5febcc95536f63d2d1dada357a73c6 --- neutronclient/neutron/v2_0/__init__.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index b34be99..1835d8d 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -21,7 +21,6 @@ import argparse import logging import re -from cliff.formatters import table from cliff import lister from cliff import show from oslo_serialization import jsonutils @@ -370,19 +369,6 @@ def update_dict(obj, dict, attributes): dict[attribute] = getattr(obj, attribute) -class TableFormater(table.TableFormatter): - """This class is used to keep consistency with prettytable 0.6. - - https://bugs.launchpad.net/python-neutronclient/+bug/1165962 - """ - def emit_list(self, column_names, data, stdout, parsed_args): - if column_names: - super(TableFormater, self).emit_list(column_names, data, stdout, - parsed_args) - else: - stdout.write('\n') - - # command.OpenStackCommand is abstract class so that metaclass of # subclass must be subclass of metaclass of all its base. # otherwise metaclass conflict exception is raised. @@ -405,14 +391,6 @@ class NeutronCommand(command.OpenStackCommand): shadow_resource = None parent_id = None - def __init__(self, app, app_args): - super(NeutronCommand, self).__init__(app, app_args) - # NOTE(markmcclain): This is no longer supported in cliff version 1.5.2 - # see https://bugs.launchpad.net/python-neutronclient/+bug/1265926 - - # if hasattr(self, 'formatters'): - # self.formatters['table'] = TableFormater() - @property def cmd_resource(self): if self.shadow_resource: From 5286df8d9f6a57efc9493f783106fdc30c159f13 Mon Sep 17 00:00:00 2001 From: reedip Date: Tue, 5 Jan 2016 11:45:51 +0900 Subject: [PATCH 06/57] Adding a lowercase converter in utils.py Certain NeutronClient CLIs require input in lower cases. In order to allow user to provide input in a case-insensitive format, similar to the uppercase function already existing in utils.py, this patch adds a lowercase function. Reference: https://review.openstack.org/#/c/140628/16..18/neutronclient/common/utils.py Change-Id: I3ad44a24a3a341209d3d3a033691b09d98b72e55 --- neutronclient/common/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/neutronclient/common/utils.py b/neutronclient/common/utils.py index 9c05675..4f19dac 100644 --- a/neutronclient/common/utils.py +++ b/neutronclient/common/utils.py @@ -46,6 +46,10 @@ def convert_to_uppercase(string): return string.upper() +def convert_to_lowercase(string): + return string.lower() + + def get_client_class(api_name, version, version_map): """Returns the client class for the requested API version. From 3a4d49d9dfb08a6e2f2702c7e6cc71d51b3146a4 Mon Sep 17 00:00:00 2001 From: Ukesh Kumar Vasudevan Date: Fri, 18 Dec 2015 08:42:48 +0000 Subject: [PATCH 07/57] Enhance the help info of "neutron router-gateway-set" The help info of "neutron router-gateway-set" is not much complete. Below is the help message of "neutron router-gateway-set": usage: neutron router-gateway-set [-h] [--request-format {json,xml}] [--disable-snat] [--fixed-ip FIXED_IP] ROUTER EXTERNAL-NETWORK neutron router-gateway-set: error: too few arguments Modified the help message and added unit test. Usage should be as below, usage: neutron router-gateway-set [-h] [--request-format {json,xml}] [--disable-snat] [--fixed-ip subnet_id=SUBNET,ip_address=IP_ADDR] ROUTER EXTERNAL-NETWORK neutron router-gateway-set: error: too few arguments Change-Id: I2a0bbd147803108e4001ffe86594e1201b9c388b Closes-Bug: 1527129 --- neutronclient/neutron/v2_0/router.py | 5 ++++- neutronclient/tests/unit/test_cli20_router.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/neutronclient/neutron/v2_0/router.py b/neutronclient/neutron/v2_0/router.py index 955c7b8..4e4066b 100644 --- a/neutronclient/neutron/v2_0/router.py +++ b/neutronclient/neutron/v2_0/router.py @@ -226,9 +226,12 @@ class SetGatewayRouter(neutronV20.NeutronCommand): '--disable-snat', action='store_true', help=_('Disable source NAT on the router gateway.')) parser.add_argument( - '--fixed-ip', action='append', + '--fixed-ip', metavar='subnet_id=SUBNET,ip_address=IP_ADDR', + action='append', help=_('Desired IP and/or subnet on external network: ' 'subnet_id=,ip_address=. ' + 'You can specify both of subnet_id and ip_address or ' + 'specify one of them as well. ' 'You can repeat this option.')) return parser diff --git a/neutronclient/tests/unit/test_cli20_router.py b/neutronclient/tests/unit/test_cli20_router.py index d69552c..66372f8 100644 --- a/neutronclient/tests/unit/test_cli20_router.py +++ b/neutronclient/tests/unit/test_cli20_router.py @@ -383,6 +383,21 @@ class CLITestV20RouterJSON(test_cli20.CLITestV20Base): {"subnet_id": "mysubnet"}]}} ) + def test_set_gateway_external_ip_and_subnet(self): + # set external gateway for router: myid externalid --fixed-ip ... + resource = 'router' + cmd = router.SetGatewayRouter(test_cli20.MyApp(sys.stdout), None) + args = ['myid', 'externalid', '--fixed-ip', + 'ip_address=10.0.0.2,subnet_id=mysubnet'] + self._test_update_resource(resource, cmd, 'myid', + args, + {"external_gateway_info": + {"network_id": "externalid", + "external_fixed_ips": [ + {"subnet_id": "mysubnet", + "ip_address": "10.0.0.2"}]}} + ) + def test_remove_gateway(self): # Remove external gateway from router: externalid. resource = 'router' From 538c23a9e0fb8e3c19b28bd1ceaa225f09f7a6e9 Mon Sep 17 00:00:00 2001 From: Ryan Tidwell Date: Mon, 12 Oct 2015 13:42:33 -0700 Subject: [PATCH 08/57] Add support for ip_version on AddressScope create Address scopes must now be created with an explicit ip_version. This patchenables ip_version to be passed on the neutron CLI. Change-Id: Iee902e1539d2bc0cb03c24f483ab7bab8b6a657e Depends-On: Ibc6de08e0ef58a5da954d13f274f6003012a76cd Partially-Implements: blueprint address-scopes --- neutronclient/neutron/v2_0/address_scope.py | 11 +++- .../tests/unit/test_cli20_address_scope.py | 57 ++++++++++++++----- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/neutronclient/neutron/v2_0/address_scope.py b/neutronclient/neutron/v2_0/address_scope.py index a79590b..8a89282 100755 --- a/neutronclient/neutron/v2_0/address_scope.py +++ b/neutronclient/neutron/v2_0/address_scope.py @@ -22,7 +22,7 @@ class ListAddressScope(neutronV20.ListCommand): """List address scopes that belong to a given tenant.""" resource = 'address_scope' - list_columns = ['id', 'name'] + list_columns = ['id', 'name', 'ip_version'] pagination_support = True sorting_support = True @@ -46,9 +46,16 @@ class CreateAddressScope(neutronV20.CreateCommand): parser.add_argument( 'name', help=_('Specify the name of the address scope.')) + parser.add_argument( + 'ip_version', + metavar='IP_VERSION', + type=int, + choices=[4, 6], + help=_('Specify the address family of the address scope.')) def args2body(self, parsed_args): - body = {'name': parsed_args.name} + body = {'name': parsed_args.name, + 'ip_version': parsed_args.ip_version} if parsed_args.shared: body['shared'] = True neutronV20.update_dict(parsed_args, body, ['tenant_id']) diff --git a/neutronclient/tests/unit/test_cli20_address_scope.py b/neutronclient/tests/unit/test_cli20_address_scope.py index 9b98631..481f231 100755 --- a/neutronclient/tests/unit/test_cli20_address_scope.py +++ b/neutronclient/tests/unit/test_cli20_address_scope.py @@ -30,19 +30,46 @@ class CLITestV20AddressScopeJSON(test_cli20.CLITestV20Base): def setUp(self): super(CLITestV20AddressScopeJSON, self).setUp(plurals={'tags': 'tag'}) - def test_create_address_scope_with_minimum_option(self): - # Create address_scope: foo-address-scope with minimum option. + def test_create_address_scope_with_minimum_option_ipv4(self): + """Create address_scope: foo-address-scope with minimum option.""" resource = 'address_scope' cmd = address_scope.CreateAddressScope( test_cli20.MyApp(sys.stdout), None) name = 'foo-address-scope' myid = 'myid' - args = [name] - position_names = ['name'] - position_values = [name] + args = [name, '4'] + position_names = ['name', 'ip_version'] + position_values = [name, 4] self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) + def test_create_address_scope_with_minimum_option_ipv6(self): + """Create address_scope: foo-address-scope with minimum option.""" + resource = 'address_scope' + cmd = address_scope.CreateAddressScope( + test_cli20.MyApp(sys.stdout), None) + name = 'foo-address-scope' + myid = 'myid' + args = [name, '6'] + position_names = ['name', 'ip_version'] + position_values = [name, 6] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_create_address_scope_with_minimum_option_bad_ip_version(self): + """Create address_scope: foo-address-scope with minimum option.""" + resource = 'address_scope' + cmd = address_scope.CreateAddressScope( + test_cli20.MyApp(sys.stdout), None) + name = 'foo-address-scope' + myid = 'myid' + args = [name, '5'] + position_names = ['name', 'ip_version'] + position_values = [name, 5] + self.assertRaises(SystemExit, self._test_create_resource, + resource, cmd, name, myid, args, position_names, + position_values) + def test_create_address_scope_with_all_option(self): # Create address_scope: foo-address-scope with all options. resource = 'address_scope' @@ -50,9 +77,9 @@ class CLITestV20AddressScopeJSON(test_cli20.CLITestV20Base): test_cli20.MyApp(sys.stdout), None) name = 'foo-address-scope' myid = 'myid' - args = [name, '--shared'] - position_names = ['name', 'shared'] - position_values = [name, True] + args = [name, '4', '--shared'] + position_names = ['name', 'ip_version', 'shared'] + position_values = [name, 4, True] self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) @@ -62,10 +89,11 @@ class CLITestV20AddressScopeJSON(test_cli20.CLITestV20Base): cmd = address_scope.CreateAddressScope( test_cli20.MyApp(sys.stdout), None) name = u'\u7f51\u7edc' + ip_version = u'4' myid = 'myid' - args = [name] - position_names = ['name'] - position_values = [name] + args = [name, ip_version] + position_names = ['name', 'ip_version'] + position_values = [name, 4] self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) @@ -115,7 +143,7 @@ class CLITestV20AddressScopeJSON(test_cli20.CLITestV20Base): cmd = address_scope.ListAddressScope(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, - sort_key=["name", "id"], + sort_key=["name", "id", "ip_version"], sort_dir=["asc", "desc"]) def test_list_address_scope_limit(self): @@ -130,9 +158,10 @@ class CLITestV20AddressScopeJSON(test_cli20.CLITestV20Base): resource = 'address_scope' cmd = address_scope.ShowAddressScope( test_cli20.MyApp(sys.stdout), None) - args = ['--fields', 'id', '--fields', 'name', self.test_id] + args = ['--fields', 'id', '--fields', 'name', self.test_id, + '--fields', 'ip_version', '6'] self._test_show_resource(resource, cmd, self.test_id, args, - ['id', 'name']) + ['id', 'name', 'ip_version']) def test_delete_address_scope(self): # Delete address_scope: address_scope_id. From 09030171037027d54445a6cfa36481b302373cc4 Mon Sep 17 00:00:00 2001 From: linwwu Date: Fri, 8 Jan 2016 22:51:53 +0800 Subject: [PATCH 09/57] Remove 'u' displayed before subnetpool-list's prefixes There is useless 'u' in the showing of "neutron subnetpool-list", Please check bug/1531418 for details. Add formatter as subnet did in the code. Change-Id: I4e1502c379032da240ad51cce8be8c1ca558ef15 Closes-Bug: #1531418 --- neutronclient/neutron/v2_0/subnetpool.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/neutronclient/neutron/v2_0/subnetpool.py b/neutronclient/neutron/v2_0/subnetpool.py index 8677cd5..a8b9461 100644 --- a/neutronclient/neutron/v2_0/subnetpool.py +++ b/neutronclient/neutron/v2_0/subnetpool.py @@ -18,6 +18,13 @@ from neutronclient._i18n import _ from neutronclient.neutron import v2_0 as neutronV20 +def _format_prefixes(subnetpool): + try: + return '\n'.join(pool for pool in subnetpool['prefixes']) + except (TypeError, KeyError): + return subnetpool['prefixes'] + + def add_updatable_arguments(parser): parser.add_argument( '--min-prefixlen', type=int, @@ -43,6 +50,7 @@ def updatable_args2body(parsed_args, body, for_create=True): class ListSubnetPool(neutronV20.ListCommand): """List subnetpools that belong to a given tenant.""" + _formatters = {'prefixes': _format_prefixes, } resource = 'subnetpool' list_columns = ['id', 'name', 'prefixes', 'default_prefixlen', 'address_scope_id'] From aac9356e00d99941f500a8866b48f55dde5a6c25 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 13 Jan 2016 08:19:08 +0000 Subject: [PATCH 10/57] Updated from global requirements Change-Id: I1d16b2a589d05b1b46e7655ea45460edeb6ac6a3 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 1ef23ba..9bad1cc 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -16,4 +16,4 @@ requests-mock>=0.7.0 # Apache-2.0 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 testrepository>=0.0.18 testtools>=1.4.0 -tempest-lib>=0.12.0 +tempest-lib>=0.13.0 From 745148817bacacc26ff06a8470cf52c815f6565a Mon Sep 17 00:00:00 2001 From: IWAMOTO Toshihiro Date: Wed, 13 Jan 2016 18:06:57 +0900 Subject: [PATCH 11/57] Convert remaining use of neutronclient.i18n to _i18n Change-Id: I77f168af92ae51ce16bed4988bbcaf7c18557727 Related-Bug: 1519493 --- neutronclient/neutron/v2_0/availability_zone.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neutronclient/neutron/v2_0/availability_zone.py b/neutronclient/neutron/v2_0/availability_zone.py index 761a50b..2081d09 100644 --- a/neutronclient/neutron/v2_0/availability_zone.py +++ b/neutronclient/neutron/v2_0/availability_zone.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from neutronclient.i18n import _ +from neutronclient._i18n import _ from neutronclient.neutron import v2_0 as neutronv20 From f903ad9eea143d03eb6f09a53bc6c3de7bc815da Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Fri, 8 Jan 2016 22:56:22 +0900 Subject: [PATCH 12/57] refactor: Remove usage of useless command.command.OpenStackCommand It seems OpenStackCommand was introduced to make neutronclient comamnds usable in OpenStack client, but at now OpenStack client directly implements cliff Command and does not require this kind of wrapper. To simplify the code, this commit drops the useless class. Partial-Bug: #1532258 Change-Id: Iaa5ce9f4b98d5c85ee6ba0303067e65a829fc78e --- neutronclient/common/command.py | 35 -------------------------- neutronclient/neutron/v2_0/__init__.py | 16 +++++++++--- neutronclient/shell.py | 15 ++++++----- 3 files changed, 22 insertions(+), 44 deletions(-) delete mode 100644 neutronclient/common/command.py diff --git a/neutronclient/common/command.py b/neutronclient/common/command.py deleted file mode 100644 index 3d05407..0000000 --- a/neutronclient/common/command.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2012 OpenStack Foundation. -# 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. -# - -from cliff import command - - -class OpenStackCommand(command.Command): - """Base class for OpenStack commands.""" - - api = None - - def run(self, parsed_args): - if not self.api: - return - else: - return super(OpenStackCommand, self).run(parsed_args) - - def get_data(self, parsed_args): - pass - - def take_action(self, parsed_args): - return self.get_data(parsed_args) diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index 2bffb5f..3aeb26a 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -21,13 +21,13 @@ import argparse import logging import re +from cliff import command from cliff import lister from cliff import show from oslo_serialization import jsonutils import six from neutronclient._i18n import _ -from neutronclient.common import command from neutronclient.common import exceptions from neutronclient.common import utils @@ -369,7 +369,7 @@ def update_dict(obj, dict, attributes): dict[attribute] = getattr(obj, attribute) -# command.OpenStackCommand is abstract class so that metaclass of +# cliff.command.Command is abstract class so that metaclass of # subclass must be subclass of metaclass of all its base. # otherwise metaclass conflict exception is raised. class NeutronCommandMeta(abc.ABCMeta): @@ -382,8 +382,10 @@ class NeutronCommandMeta(abc.ABCMeta): @six.add_metaclass(NeutronCommandMeta) -class NeutronCommand(command.OpenStackCommand): +class NeutronCommand(command.Command): + # TODO(amotoki): Get rid of 'api' attribute. There is no such convention + # in OpenStack client. It may be an ancient convention though. api = 'network' values_specs = [] json_indent = None @@ -391,6 +393,14 @@ class NeutronCommand(command.OpenStackCommand): shadow_resource = None parent_id = None + # TODO(amotoki): Remove the usage of get_data and use take_action directly. + # Overriding take_action() is recommended when implementing cliff command. + def get_data(self, parsed_args): + pass + + def take_action(self, parsed_args): + return self.get_data(parsed_args) + @property def cmd_resource(self): if self.shadow_resource: diff --git a/neutronclient/shell.py b/neutronclient/shell.py index 508cdce..01771d5 100644 --- a/neutronclient/shell.py +++ b/neutronclient/shell.py @@ -32,11 +32,11 @@ import os_client_config from oslo_utils import encodeutils from cliff import app +from cliff import command from cliff import commandmanager from neutronclient._i18n import _ from neutronclient.common import clientmanager -from neutronclient.common import command as openstack_command from neutronclient.common import exceptions as exc from neutronclient.common import extension as client_extension from neutronclient.common import utils @@ -140,9 +140,12 @@ def check_non_negative_int(value): return value -class BashCompletionCommand(openstack_command.OpenStackCommand): +class BashCompletionCommand(command.Command): """Prints all of the commands and options for bash-completion.""" - resource = "bash_completion" + + def take_action(self, parsed_args): + pass + COMMAND_V2 = { 'bash-completion': BashCompletionCommand, @@ -727,9 +730,9 @@ class NeutronShell(app.App): options = set() for option, _action in self.parser._option_string_actions.items(): options.add(option) - for command_name, command in self.command_manager: - commands.add(command_name) - cmd_factory = command.load() + for _name, _command in self.command_manager: + commands.add(_name) + cmd_factory = _command.load() cmd = cmd_factory(self, None) cmd_parser = cmd.get_parser('') for option, _action in cmd_parser._option_string_actions.items(): From 2eb5b71c074805de2ca2a23c04be653ca8675410 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Fri, 8 Jan 2016 23:01:36 +0900 Subject: [PATCH 13/57] refactor: Drop meaningless 'api' attribute from NeutronCommand class 'api' attribute in NeutronCommand class was required by OpenStackCommand class. It is now meaningless. Partial-Bug: #1532258 Change-Id: I7fa24cc1011e5ee0ce1c8956edae785cfb4b9408 --- neutronclient/neutron/v2_0/__init__.py | 8 -------- neutronclient/neutron/v2_0/floatingip.py | 2 -- neutronclient/neutron/v2_0/quota.py | 3 --- neutronclient/neutron/v2_0/router.py | 3 --- 4 files changed, 16 deletions(-) diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index 3aeb26a..80c0131 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -384,9 +384,6 @@ class NeutronCommandMeta(abc.ABCMeta): @six.add_metaclass(NeutronCommandMeta) class NeutronCommand(command.Command): - # TODO(amotoki): Get rid of 'api' attribute. There is no such convention - # in OpenStack client. It may be an ancient convention though. - api = 'network' values_specs = [] json_indent = None resource = None @@ -456,7 +453,6 @@ class NeutronCommand(command.Command): class CreateCommand(NeutronCommand, show.ShowOne): """Create a resource for a given tenant.""" - api = 'network' log = None def get_parser(self, prog_name): @@ -498,7 +494,6 @@ class CreateCommand(NeutronCommand, show.ShowOne): class UpdateCommand(NeutronCommand): """Update resource's information.""" - api = 'network' log = None allow_names = True @@ -553,7 +548,6 @@ class UpdateCommand(NeutronCommand): class DeleteCommand(NeutronCommand): """Delete a given resource.""" - api = 'network' log = None allow_names = True @@ -599,7 +593,6 @@ class DeleteCommand(NeutronCommand): class ListCommand(NeutronCommand, lister.Lister): """List resources that belong to a given tenant.""" - api = 'network' log = None _formatters = {} list_columns = [] @@ -705,7 +698,6 @@ class ListCommand(NeutronCommand, lister.Lister): class ShowCommand(NeutronCommand, show.ShowOne): """Show information of a given resource.""" - api = 'network' log = None allow_names = True diff --git a/neutronclient/neutron/v2_0/floatingip.py b/neutronclient/neutron/v2_0/floatingip.py index 44b402a..7e634e9 100644 --- a/neutronclient/neutron/v2_0/floatingip.py +++ b/neutronclient/neutron/v2_0/floatingip.py @@ -90,7 +90,6 @@ class DeleteFloatingIP(neutronV20.DeleteCommand): class AssociateFloatingIP(neutronV20.NeutronCommand): """Create a mapping between a floating IP and a fixed IP.""" - api = 'network' resource = 'floatingip' def get_parser(self, prog_name): @@ -126,7 +125,6 @@ class AssociateFloatingIP(neutronV20.NeutronCommand): class DisassociateFloatingIP(neutronV20.NeutronCommand): """Remove a mapping from a floating IP to a fixed IP.""" - api = 'network' resource = 'floatingip' def get_parser(self, prog_name): diff --git a/neutronclient/neutron/v2_0/quota.py b/neutronclient/neutron/v2_0/quota.py index 4b6e86e..2ccc545 100644 --- a/neutronclient/neutron/v2_0/quota.py +++ b/neutronclient/neutron/v2_0/quota.py @@ -37,7 +37,6 @@ def get_tenant_id(args, client): class DeleteQuota(neutronV20.NeutronCommand): """Delete defined quotas of a given tenant.""" - api = 'network' resource = 'quota' def get_parser(self, prog_name): @@ -70,7 +69,6 @@ class DeleteQuota(neutronV20.NeutronCommand): class ListQuota(neutronV20.NeutronCommand, lister.Lister): """List quotas of all tenants who have non-default quota values.""" - api = 'network' resource = 'quota' def get_parser(self, prog_name): @@ -98,7 +96,6 @@ class ShowQuota(neutronV20.NeutronCommand, show.ShowOne): """Show quotas of a given tenant. """ - api = 'network' resource = "quota" def get_parser(self, prog_name): diff --git a/neutronclient/neutron/v2_0/router.py b/neutronclient/neutron/v2_0/router.py index 4e4066b..88da856 100644 --- a/neutronclient/neutron/v2_0/router.py +++ b/neutronclient/neutron/v2_0/router.py @@ -138,7 +138,6 @@ class UpdateRouter(neutronV20.UpdateCommand): class RouterInterfaceCommand(neutronV20.NeutronCommand): """Based class to Add/Remove router interface.""" - api = 'network' resource = 'router' def call_api(self, neutron_client, router_id, body): @@ -211,7 +210,6 @@ class RemoveInterfaceRouter(RouterInterfaceCommand): class SetGatewayRouter(neutronV20.NeutronCommand): """Set the external network gateway for a router.""" - api = 'network' resource = 'router' def get_parser(self, prog_name): @@ -264,7 +262,6 @@ class SetGatewayRouter(neutronV20.NeutronCommand): class RemoveGatewayRouter(neutronV20.NeutronCommand): """Remove an external network gateway from a router.""" - api = 'network' resource = 'router' def get_parser(self, prog_name): From 131160ed5605430c3c538133e28ed3bcbe4bc231 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Fri, 8 Jan 2016 23:11:07 +0900 Subject: [PATCH 14/57] refactor: Get rid of usage of get_data in favor of take_action Previously the dropped OpenStackCommand uses get_data() as an interface which subclasses should implement. On the other hand, cliff Command recommends to implement take_action() in subclasses and there is no reason not to follow this convention. Partial-Bug: #1532258 Change-Id: I164d6f31152df621611985a84eef76e7dfa54d44 --- neutronclient/common/extension.py | 12 +++++------ neutronclient/neutron/v2_0/__init__.py | 20 +++++++++---------- neutronclient/neutron/v2_0/lb/pool.py | 2 +- .../neutron/v2_0/lb/v2/loadbalancer.py | 2 +- neutronclient/neutron/v2_0/lb/v2/member.py | 4 ++-- neutronclient/neutron/v2_0/port.py | 4 ++-- neutronclient/neutron/v2_0/quota.py | 10 +++++----- .../tests/unit/test_cli20_network.py | 2 +- .../tests/unit/test_cli20_securitygroup.py | 2 +- 9 files changed, 28 insertions(+), 30 deletions(-) diff --git a/neutronclient/common/extension.py b/neutronclient/common/extension.py index 2ff8d34..f7e361b 100644 --- a/neutronclient/common/extension.py +++ b/neutronclient/common/extension.py @@ -31,26 +31,26 @@ class NeutronClientExtension(neutronV20.NeutronCommand): class ClientExtensionShow(NeutronClientExtension, neutronV20.ShowCommand): - def get_data(self, parsed_args): + def take_action(self, parsed_args): # NOTE(mdietz): Calls 'execute' to provide a consistent pattern # for any implementers adding extensions with # regard to any other extension verb. return self.execute(parsed_args) def execute(self, parsed_args): - return super(ClientExtensionShow, self).get_data(parsed_args) + return super(ClientExtensionShow, self).take_action(parsed_args) class ClientExtensionList(NeutronClientExtension, neutronV20.ListCommand): - def get_data(self, parsed_args): + def take_action(self, parsed_args): # NOTE(mdietz): Calls 'execute' to provide a consistent pattern # for any implementers adding extensions with # regard to any other extension verb. return self.execute(parsed_args) def execute(self, parsed_args): - return super(ClientExtensionList, self).get_data(parsed_args) + return super(ClientExtensionList, self).take_action(parsed_args) class ClientExtensionDelete(NeutronClientExtension, neutronV20.DeleteCommand): @@ -65,14 +65,14 @@ class ClientExtensionDelete(NeutronClientExtension, neutronV20.DeleteCommand): class ClientExtensionCreate(NeutronClientExtension, neutronV20.CreateCommand): - def get_data(self, parsed_args): + def take_action(self, parsed_args): # NOTE(mdietz): Calls 'execute' to provide a consistent pattern # for any implementers adding extensions with # regard to any other extension verb. return self.execute(parsed_args) def execute(self, parsed_args): - return super(ClientExtensionCreate, self).get_data(parsed_args) + return super(ClientExtensionCreate, self).take_action(parsed_args) class ClientExtensionUpdate(NeutronClientExtension, neutronV20.UpdateCommand): diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index 80c0131..300e98e 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -390,11 +390,9 @@ class NeutronCommand(command.Command): shadow_resource = None parent_id = None - # TODO(amotoki): Remove the usage of get_data and use take_action directly. - # Overriding take_action() is recommended when implementing cliff command. - def get_data(self, parsed_args): - pass - + # TODO(amotoki): Remove take_action here. It should be an abstract method + # as cliff.command.Command does. To do this, we need to avoid overriding + # run() directly. def take_action(self, parsed_args): return self.get_data(parsed_args) @@ -466,8 +464,8 @@ class CreateCommand(NeutronCommand, show.ShowOne): self.add_known_arguments(parser) return parser - def get_data(self, parsed_args): - self.log.debug('get_data(%s)' % parsed_args) + def take_action(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) self.set_extra_attrs(parsed_args) neutron_client = self.get_client() _extra_values = parse_args_to_dict(self.values_specs) @@ -687,8 +685,8 @@ class ListCommand(NeutronCommand, lister.Lister): s, _columns, formatters=formatters, ) for s in info), ) - def get_data(self, parsed_args): - self.log.debug('get_data(%s)', parsed_args) + def take_action(self, parsed_args): + self.log.debug('run(%s)', parsed_args) self.set_extra_attrs(parsed_args) data = self.retrieve_list(parsed_args) self.extend_list(data, parsed_args) @@ -714,8 +712,8 @@ class ShowCommand(NeutronCommand, show.ShowOne): self.add_known_arguments(parser) return parser - def get_data(self, parsed_args): - self.log.debug('get_data(%s)', parsed_args) + def take_action(self, parsed_args): + self.log.debug('run(%s)', parsed_args) self.set_extra_attrs(parsed_args) neutron_client = self.get_client() diff --git a/neutronclient/neutron/v2_0/lb/pool.py b/neutronclient/neutron/v2_0/lb/pool.py index aff529e..86497a7 100644 --- a/neutronclient/neutron/v2_0/lb/pool.py +++ b/neutronclient/neutron/v2_0/lb/pool.py @@ -107,7 +107,7 @@ class RetrievePoolStats(neutronV20.ShowCommand): resource = 'pool' - def get_data(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() pool_id = neutronV20.find_resourceid_by_name_or_id( diff --git a/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py b/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py index 065ac54..bfa4f9c 100644 --- a/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py +++ b/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py @@ -100,7 +100,7 @@ class RetrieveLoadBalancerStats(neutronV20.ShowCommand): resource = 'loadbalancer' - def get_data(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() neutron_client.format = parsed_args.request_format diff --git a/neutronclient/neutron/v2_0/lb/v2/member.py b/neutronclient/neutron/v2_0/lb/v2/member.py index a5a7751..28109c1 100644 --- a/neutronclient/neutron/v2_0/lb/v2/member.py +++ b/neutronclient/neutron/v2_0/lb/v2/member.py @@ -49,10 +49,10 @@ class ListMember(LbaasMemberMixin, neutronV20.ListCommand): pagination_support = True sorting_support = True - def get_data(self, parsed_args): + def take_action(self, parsed_args): self.parent_id = _get_pool_id(self.get_client(), parsed_args.pool) self.values_specs.append('--pool_id=%s' % self.parent_id) - return super(ListMember, self).get_data(parsed_args) + return super(ListMember, self).take_action(parsed_args) class ShowMember(LbaasMemberMixin, neutronV20.ShowCommand): diff --git a/neutronclient/neutron/v2_0/port.py b/neutronclient/neutron/v2_0/port.py index 719d4ad..6c997f4 100644 --- a/neutronclient/neutron/v2_0/port.py +++ b/neutronclient/neutron/v2_0/port.py @@ -111,12 +111,12 @@ class ListRouterPort(neutronV20.ListCommand): help=_('ID or name of router to look up.')) return parser - def get_data(self, parsed_args): + def take_action(self, parsed_args): neutron_client = self.get_client() _id = neutronV20.find_resourceid_by_name_or_id( neutron_client, 'router', parsed_args.id) self.values_specs.append('--device_id=%s' % _id) - return super(ListRouterPort, self).get_data(parsed_args) + return super(ListRouterPort, self).take_action(parsed_args) class ShowPort(neutronV20.ShowCommand): diff --git a/neutronclient/neutron/v2_0/quota.py b/neutronclient/neutron/v2_0/quota.py index 2ccc545..a88f0c6 100644 --- a/neutronclient/neutron/v2_0/quota.py +++ b/neutronclient/neutron/v2_0/quota.py @@ -75,8 +75,8 @@ class ListQuota(neutronV20.NeutronCommand, lister.Lister): parser = super(ListQuota, self).get_parser(prog_name) return parser - def get_data(self, parsed_args): - self.log.debug('get_data(%s)', parsed_args) + def take_action(self, parsed_args): + self.log.debug('run(%s)', parsed_args) neutron_client = self.get_client() search_opts = {} self.log.debug('search options: %s', search_opts) @@ -114,8 +114,8 @@ class ShowQuota(neutronV20.NeutronCommand, show.ShowOne): help=argparse.SUPPRESS, nargs='?') return parser - def get_data(self, parsed_args): - self.log.debug('get_data(%s)', parsed_args) + def take_action(self, parsed_args): + self.log.debug('run(%s)', parsed_args) neutron_client = self.get_client() tenant_id = get_tenant_id(parsed_args, neutron_client) params = {} @@ -213,7 +213,7 @@ class UpdateQuota(neutronV20.NeutronCommand, show.ShowOne): getattr(parsed_args, resource)) return {self.resource: quota} - def get_data(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)', parsed_args) neutron_client = self.get_client() _extra_values = neutronV20.parse_args_to_dict(self.values_specs) diff --git a/neutronclient/tests/unit/test_cli20_network.py b/neutronclient/tests/unit/test_cli20_network.py index 4909565..8d264c4 100644 --- a/neutronclient/tests/unit/test_cli20_network.py +++ b/neutronclient/tests/unit/test_cli20_network.py @@ -288,7 +288,7 @@ class CLITestV20NetworkJSON(test_cli20.CLITestV20Base): args = [] cmd_parser = cmd.get_parser('list_networks') parsed_args = cmd_parser.parse_args(args) - result = cmd.get_data(parsed_args) + result = cmd.take_action(parsed_args) self.mox.VerifyAll() self.mox.UnsetStubs() _result = [x for x in result[1]] diff --git a/neutronclient/tests/unit/test_cli20_securitygroup.py b/neutronclient/tests/unit/test_cli20_securitygroup.py index 721d08c..07c6a36 100644 --- a/neutronclient/tests/unit/test_cli20_securitygroup.py +++ b/neutronclient/tests/unit/test_cli20_securitygroup.py @@ -407,7 +407,7 @@ class CLITestV20SecurityGroupsJSON(test_cli20.CLITestV20Base): cmd_parser = cmd.get_parser('list_security_group_rules') parsed_args = cmd_parser.parse_args(args) - result = cmd.get_data(parsed_args) + result = cmd.take_action(parsed_args) self.mox.VerifyAll() self.mox.UnsetStubs() # Check columns From 2f08273be94db6f77f0b6d79fcff79746935d0d5 Mon Sep 17 00:00:00 2001 From: Irina Date: Mon, 18 Jan 2016 18:54:08 +0800 Subject: [PATCH 15/57] Fix typo in docstrings Change 'formating' to 'formatting'. Change-Id: If48e47a674dbb454f41ee429b310d26e8404f8a2 --- neutronclient/v2_0/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index fed5b13..7c9bd31 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -88,7 +88,7 @@ def exception_handler_v20(status_code, error_content): class APIParamsCall(object): - """A Decorator to support formating and tenant overriding and filters.""" + """A Decorator to support formatting and tenant overriding and filters.""" def __init__(self, function): self.function = function From b12d19df92311f43d1d21e8b7232a7b41f9a6801 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 19 Jan 2016 12:08:12 +0000 Subject: [PATCH 16/57] Updated from global requirements Change-Id: Ieb89e1291194e947c444a5a3bfce5423b5797c1d --- requirements.txt | 22 +++++++++++----------- test-requirements.txt | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/requirements.txt b/requirements.txt index 58b4593..4dd2f60 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,18 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=1.6 -argparse +pbr>=1.6 # Apache-2.0 +argparse # PSF cliff>=1.15.0 # Apache-2.0 debtcollector>=0.3.0 # Apache-2.0 -iso8601>=0.1.9 -netaddr!=0.7.16,>=0.7.12 +iso8601>=0.1.9 # MIT +netaddr!=0.7.16,>=0.7.12 # BSD oslo.i18n>=1.5.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 -oslo.utils>=3.2.0 # Apache-2.0 -os-client-config>=1.13.1 -keystoneauth1>=2.1.0 -requests!=2.9.0,>=2.8.1 -simplejson>=2.2.0 -six>=1.9.0 -Babel>=1.3 +oslo.utils>=3.4.0 # Apache-2.0 +os-client-config>=1.13.1 # Apache-2.0 +keystoneauth1>=2.1.0 # Apache-2.0 +requests!=2.9.0,>=2.8.1 # Apache-2.0 +simplejson>=2.2.0 # MIT +six>=1.9.0 # MIT +Babel>=1.3 # BSD diff --git a/test-requirements.txt b/test-requirements.txt index 9bad1cc..35c9b6b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,17 +3,17 @@ # process, which may cause wedges in the gate later. hacking<0.11,>=0.10.0 -coverage>=3.6 -discover -fixtures>=1.3.1 -mox3>=0.7.0 -mock>=1.2 +coverage>=3.6 # Apache-2.0 +discover # BSD +fixtures>=1.3.1 # Apache-2.0/BSD +mox3>=0.7.0 # Apache-2.0 +mock>=1.2 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 -python-subunit>=0.0.18 +python-subunit>=0.0.18 # Apache-2.0/BSD reno>=0.1.1 # Apache2 requests-mock>=0.7.0 # Apache-2.0 -sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 -testrepository>=0.0.18 -testtools>=1.4.0 -tempest-lib>=0.13.0 +sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD +testrepository>=0.0.18 # Apache-2.0/BSD +testtools>=1.4.0 # MIT +tempest-lib>=0.13.0 # Apache-2.0 From 047bbd90778535ba2a47eef19cb92faf7ce37bce Mon Sep 17 00:00:00 2001 From: John Davidge Date: Tue, 20 Oct 2015 12:56:56 +0100 Subject: [PATCH 17/57] Add support for default subnetpools API Adds support for creating, viewing, and updating subnetpools with the new 'is_default' property. This feature will be used by OpenStack admins to manage the default subnetpools used by their tenants. This feature replaces the old configuration options for default subnetpools so that the neutron service no longer needs to be restarted after changes. Creating a new default subnetpool is achieved by adding the flag: '--is-default True' to the subnetpool-create call. For example: neutron subnetpool-create --pool-prefix 10.0.0.0/24 --is_default True The above will result in the creation of a new default subnetpool, only if a default does not already exist for the given IP version. Only one default may exist for each IP version. Similarly, 'is-default True' and '--is-default False' can be used when calling subnetpool-update. This feature can only be used by the cloud admin. All users can see the is_default value in subnetpool-list and subnetpool-show. Includes release notes. DocImpact Change-Id: I80ad2a1407266eff5c66c75974d3cc467701a75e Closes-Bug: 1508012 --- neutronclient/neutron/v2_0/subnetpool.py | 9 +++++++-- .../tests/unit/test_cli20_subnetpool.py | 20 +++++++++++++++++++ ...t-subnetpool-support-c0d34870e9d3e814.yaml | 9 +++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/default-subnetpool-support-c0d34870e9d3e814.yaml diff --git a/neutronclient/neutron/v2_0/subnetpool.py b/neutronclient/neutron/v2_0/subnetpool.py index 8677cd5..f94c28b 100644 --- a/neutronclient/neutron/v2_0/subnetpool.py +++ b/neutronclient/neutron/v2_0/subnetpool.py @@ -15,6 +15,7 @@ # from neutronclient._i18n import _ +from neutronclient.common import utils from neutronclient.neutron import v2_0 as neutronV20 @@ -32,12 +33,16 @@ def add_updatable_arguments(parser): '--pool-prefix', action='append', dest='prefixes', help=_('Subnetpool prefixes (This option can be repeated).')) + utils.add_boolean_argument( + parser, '--is-default', + help=_('Specify whether this should be the default subnetpool ' + '(True meaning default).')) def updatable_args2body(parsed_args, body, for_create=True): neutronV20.update_dict(parsed_args, body, ['name', 'prefixes', 'default_prefixlen', - 'min_prefixlen', 'max_prefixlen']) + 'min_prefixlen', 'max_prefixlen', 'is_default']) class ListSubnetPool(neutronV20.ListCommand): @@ -45,7 +50,7 @@ class ListSubnetPool(neutronV20.ListCommand): resource = 'subnetpool' list_columns = ['id', 'name', 'prefixes', - 'default_prefixlen', 'address_scope_id'] + 'default_prefixlen', 'address_scope_id', 'is_default'] pagination_support = True sorting_support = True diff --git a/neutronclient/tests/unit/test_cli20_subnetpool.py b/neutronclient/tests/unit/test_cli20_subnetpool.py index dd743aa..60d25de 100644 --- a/neutronclient/tests/unit/test_cli20_subnetpool.py +++ b/neutronclient/tests/unit/test_cli20_subnetpool.py @@ -63,6 +63,26 @@ class CLITestV20SubnetPoolJSON(test_cli20.CLITestV20Base): self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) + def test_create_subnetpool(self, default='false'): + # Create subnetpool: myname. + resource = 'subnetpool' + cmd = subnetpool.CreateSubnetPool(test_cli20.MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + min_prefixlen = 30 + prefix1 = '10.11.12.0/24' + prefix2 = '12.11.13.0/24' + args = [name, '--min-prefixlen', str(min_prefixlen), + '--pool-prefix', prefix1, '--pool-prefix', prefix2, + '--is-default', default] + position_names = ['name', 'min_prefixlen', 'prefixes', 'is_default'] + position_values = [name, min_prefixlen, [prefix1, prefix2], default] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_create_subnetpool_default(self): + self.test_create_subnetpool(default='true') + def test_create_subnetpool_with_unicode(self): # Create subnetpool: u'\u7f51\u7edc'. resource = 'subnetpool' diff --git a/releasenotes/notes/default-subnetpool-support-c0d34870e9d3e814.yaml b/releasenotes/notes/default-subnetpool-support-c0d34870e9d3e814.yaml new file mode 100644 index 0000000..2a571d6 --- /dev/null +++ b/releasenotes/notes/default-subnetpool-support-c0d34870e9d3e814.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + CLI support for default subnetpools. + + * The ``subnetpool-list`` and ``subnetpool-show`` command output includes + the ``is_default`` field. + * The ``subnetpool-create`` and ``subnetpool-update`` commands include a + ``--is-default`` option. From a97f28f18729a55f3cdc0c7c21d6a183d6b01a1c Mon Sep 17 00:00:00 2001 From: minwang Date: Tue, 17 Nov 2015 16:53:05 -0800 Subject: [PATCH 18/57] Add code for load balancer status tree So far the feature of retrieving a specific Load Balancer's Status Tree is not implemented in the neutronclient code, we need to add feature code and related tests. DocImpact Add loadbalancer-status-tree feature in CLI Change-Id: Ia7804ab6baac674830c6834f67cfd411ebf4d14f --- .../neutron/v2_0/lb/v2/loadbalancer.py | 29 +++++++++++++++ neutronclient/shell.py | 1 + .../unit/lb/v2/test_cli20_loadbalancer.py | 37 +++++++++++++++++++ neutronclient/v2_0/client.py | 7 ++++ .../add-lb-status-tree-723f23c09617de3b.yaml | 7 ++++ releasenotes/source/old_relnotes.rst | 1 + 6 files changed, 82 insertions(+) create mode 100644 releasenotes/notes/add-lb-status-tree-723f23c09617de3b.yaml diff --git a/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py b/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py index 065ac54..caef541 100644 --- a/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py +++ b/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. # +from oslo_serialization import jsonutils from neutronclient._i18n import _ from neutronclient.neutron import v2_0 as neutronV20 @@ -128,3 +129,31 @@ class RetrieveLoadBalancerStats(neutronV20.ShowCommand): # here covert the data dict to the 1-1 vector format below: # [(field1, field2, field3, ...), (value1, value2, value3, ...)] return list(zip(*sorted(stats.items()))) + + +class RetrieveLoadBalancerStatus(neutronV20.NeutronCommand): + """Retrieve status for a given loadbalancer. + + The only output is a formatted JSON tree, and the table format + does not support this type of data. + """ + resource = 'loadbalancer' + + def get_parser(self, prog_name): + parser = super(RetrieveLoadBalancerStatus, self).get_parser(prog_name) + parser.add_argument( + self.resource, metavar=self.resource.upper(), + help=_('ID or name of %s to show.') % self.resource) + + return parser + + def take_action(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + neutron_client = self.get_client() + lb_id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, self.resource, parsed_args.loadbalancer) + params = {} + data = neutron_client.retrieve_loadbalancer_status(lb_id, **params) + res = data['statuses'] + if 'statuses' in data: + print(jsonutils.dumps(res, indent=4)) diff --git a/neutronclient/shell.py b/neutronclient/shell.py index 508cdce..d7840b8 100644 --- a/neutronclient/shell.py +++ b/neutronclient/shell.py @@ -204,6 +204,7 @@ COMMAND_V2 = { 'lbaas-loadbalancer-update': lbaas_loadbalancer.UpdateLoadBalancer, 'lbaas-loadbalancer-delete': lbaas_loadbalancer.DeleteLoadBalancer, 'lbaas-loadbalancer-stats': lbaas_loadbalancer.RetrieveLoadBalancerStats, + 'lbaas-loadbalancer-status': lbaas_loadbalancer.RetrieveLoadBalancerStatus, 'lbaas-listener-list': lbaas_listener.ListListener, 'lbaas-listener-show': lbaas_listener.ShowListener, 'lbaas-listener-create': lbaas_listener.CreateListener, diff --git a/neutronclient/tests/unit/lb/v2/test_cli20_loadbalancer.py b/neutronclient/tests/unit/lb/v2/test_cli20_loadbalancer.py index cb58c6d..205e681 100644 --- a/neutronclient/tests/unit/lb/v2/test_cli20_loadbalancer.py +++ b/neutronclient/tests/unit/lb/v2/test_cli20_loadbalancer.py @@ -166,3 +166,40 @@ class CLITestV20LbLoadBalancerJSON(test_cli20.CLITestV20Base): self.assertIn('1234', _str) self.assertIn('bytes_out', _str) self.assertIn('4321', _str) + + def test_get_loadbalancer_statuses(self): + # lbaas-loadbalancer-status test_id. + resource = 'loadbalancer' + cmd = lb.RetrieveLoadBalancerStatus(test_cli20.MyApp(sys.stdout), None) + my_id = self.test_id + args = [my_id] + + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + + expected_res = {'statuses': {'operating_status': 'ONLINE', + 'provisioning_status': 'ACTIVE'}} + + resstr = self.client.serialize(expected_res) + + path = getattr(self.client, "lbaas_loadbalancer_path_status") + return_tup = (test_cli20.MyResp(200), resstr) + self.client.httpclient.request( + test_cli20.end_url(path % my_id), 'GET', + body=None, + headers=mox.ContainsKeyValue( + 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(return_tup) + self.mox.ReplayAll() + + cmd_parser = cmd.get_parser("test_" + resource) + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + self.assertIn('operating_status', _str) + self.assertIn('ONLINE', _str) + self.assertIn('provisioning_status', _str) + self.assertIn('ACTIVE', _str) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index 4dc7382..a58e9b5 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -342,6 +342,7 @@ class Client(ClientBase): lbaas_loadbalancers_path = "/lbaas/loadbalancers" lbaas_loadbalancer_path = "/lbaas/loadbalancers/%s" lbaas_loadbalancer_path_stats = "/lbaas/loadbalancers/%s/stats" + lbaas_loadbalancer_path_status = "/lbaas/loadbalancers/%s/statuses" lbaas_listeners_path = "/lbaas/listeners" lbaas_listener_path = "/lbaas/listeners/%s" lbaas_pools_path = "/lbaas/pools" @@ -944,6 +945,12 @@ class Client(ClientBase): return self.get(self.lbaas_loadbalancer_path_stats % (loadbalancer), params=_params) + @APIParamsCall + def retrieve_loadbalancer_status(self, loadbalancer, **_params): + """Retrieves status for a certain load balancer.""" + return self.get(self.lbaas_loadbalancer_path_status % (loadbalancer), + params=_params) + @APIParamsCall def list_listeners(self, retrieve_all=True, **_params): """Fetches a list of all lbaas_listeners for a tenant.""" diff --git a/releasenotes/notes/add-lb-status-tree-723f23c09617de3b.yaml b/releasenotes/notes/add-lb-status-tree-723f23c09617de3b.yaml new file mode 100644 index 0000000..8f48d28 --- /dev/null +++ b/releasenotes/notes/add-lb-status-tree-723f23c09617de3b.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + CLI support for load balancer status tree. + + * The ``lbaas-loadbalancer-status`` command provides the status + tree of a specific load balancer. \ No newline at end of file diff --git a/releasenotes/source/old_relnotes.rst b/releasenotes/source/old_relnotes.rst index 6a571a7..0936e32 100644 --- a/releasenotes/source/old_relnotes.rst +++ b/releasenotes/source/old_relnotes.rst @@ -10,6 +10,7 @@ Old Release Notes * made the publicURL the default endpoint instead of adminURL * add ability to update security group name (requires 2013.2-Havana or later) * add flake8 and pbr support for testing and building +* add ability to retrieve a specific load balancer's status tree 2.2.0 ----- From 0259e03feaaaf6ed6742bd6512b00eb9f7fc16a4 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Wed, 20 Jan 2016 19:20:55 +0100 Subject: [PATCH 19/57] Remove argparse from requirements argparse was external in python 2.6 but not anymore, remove it from requirements. This should help with pip 8.0 that gets confused in this situation. Installation of the external argparse is not needed. Change-Id: Ib7e74912b36c1b5ccb514e31fac35efeff57378d --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4dd2f60..e649fc1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr>=1.6 # Apache-2.0 -argparse # PSF cliff>=1.15.0 # Apache-2.0 debtcollector>=0.3.0 # Apache-2.0 iso8601>=0.1.9 # MIT From c2545f869a65564d5ff231ff453272c6a3310d6c Mon Sep 17 00:00:00 2001 From: linwwu Date: Wed, 13 Jan 2016 08:22:05 +0800 Subject: [PATCH 20/57] Remove inconsistency from vpn help text Improve help text for IKEPOLICY VPNSERVICE IPSECPOLICY IPSECSITECONNECTION in python-neutronclient Fixes bug #1335160 Change-Id: I5da7efe8f8c8e6494884571b3d70237782cd314f Closes-bug: #1335160 --- neutronclient/neutron/v2_0/__init__.py | 15 ++++++++++++--- neutronclient/neutron/v2_0/vpn/ikepolicy.py | 3 +++ .../neutron/v2_0/vpn/ipsec_site_connection.py | 3 +++ neutronclient/neutron/v2_0/vpn/ipsecpolicy.py | 3 +++ neutronclient/neutron/v2_0/vpn/vpnservice.py | 3 +++ 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index d5be4a1..f44822a 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -513,6 +513,7 @@ class UpdateCommand(NeutronCommand): api = 'network' log = None allow_names = True + help_resource = None def get_parser(self, prog_name): parser = super(UpdateCommand, self).get_parser(prog_name) @@ -520,9 +521,11 @@ class UpdateCommand(NeutronCommand): help_str = _('ID or name of %s to update.') else: help_str = _('ID of %s to update.') + if not self.help_resource: + self.help_resource = self.resource parser.add_argument( 'id', metavar=self.resource.upper(), - help=help_str % self.resource) + help=help_str % self.help_resource) self.add_known_arguments(parser) return parser @@ -568,6 +571,7 @@ class DeleteCommand(NeutronCommand): api = 'network' log = None allow_names = True + help_resource = None def get_parser(self, prog_name): parser = super(DeleteCommand, self).get_parser(prog_name) @@ -575,9 +579,11 @@ class DeleteCommand(NeutronCommand): help_str = _('ID or name of %s to delete.') else: help_str = _('ID of %s to delete.') + if not self.help_resource: + self.help_resource = self.resource parser.add_argument( 'id', metavar=self.resource.upper(), - help=help_str % self.resource) + help=help_str % self.help_resource) self.add_known_arguments(parser) return parser @@ -720,6 +726,7 @@ class ShowCommand(NeutronCommand, show.ShowOne): api = 'network' log = None allow_names = True + help_resource = None def get_parser(self, prog_name): parser = super(ShowCommand, self).get_parser(prog_name) @@ -728,9 +735,11 @@ class ShowCommand(NeutronCommand, show.ShowOne): help_str = _('ID or name of %s to look up.') else: help_str = _('ID of %s to look up.') + if not self.help_resource: + self.help_resource = self.resource parser.add_argument( 'id', metavar=self.resource.upper(), - help=help_str % self.resource) + help=help_str % self.help_resource) self.add_known_arguments(parser) return parser diff --git a/neutronclient/neutron/v2_0/vpn/ikepolicy.py b/neutronclient/neutron/v2_0/vpn/ikepolicy.py index e771aad..5090831 100644 --- a/neutronclient/neutron/v2_0/vpn/ikepolicy.py +++ b/neutronclient/neutron/v2_0/vpn/ikepolicy.py @@ -35,6 +35,7 @@ class ShowIKEPolicy(neutronv20.ShowCommand): """Show information of a given IKE policy.""" resource = 'ikepolicy' + help_resource = 'IKE policy' class CreateIKEPolicy(neutronv20.CreateCommand): @@ -93,6 +94,7 @@ class UpdateIKEPolicy(neutronv20.UpdateCommand): """Update a given IKE policy.""" resource = 'ikepolicy' + help_resource = 'IKE policy' def add_known_arguments(self, parser): parser.add_argument( @@ -114,3 +116,4 @@ class DeleteIKEPolicy(neutronv20.DeleteCommand): """Delete a given IKE policy.""" resource = 'ikepolicy' + help_resource = 'IKE policy' diff --git a/neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py b/neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py index 8726c28..bace4d2 100644 --- a/neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py +++ b/neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py @@ -46,6 +46,7 @@ class ShowIPsecSiteConnection(neutronv20.ShowCommand): """Show information of a given IPsec site connection.""" resource = 'ipsec_site_connection' + help_resource = 'IPsec site connection' class IPsecSiteConnectionMixin(object): @@ -196,9 +197,11 @@ class UpdateIPsecSiteConnection(IPsecSiteConnectionMixin, """Update a given IPsec site connection.""" resource = 'ipsec_site_connection' + help_resource = 'IPsec site connection' class DeleteIPsecSiteConnection(neutronv20.DeleteCommand): """Delete a given IPsec site connection.""" resource = 'ipsec_site_connection' + help_resource = 'IPsec site connection' diff --git a/neutronclient/neutron/v2_0/vpn/ipsecpolicy.py b/neutronclient/neutron/v2_0/vpn/ipsecpolicy.py index 80a334d..76f0914 100644 --- a/neutronclient/neutron/v2_0/vpn/ipsecpolicy.py +++ b/neutronclient/neutron/v2_0/vpn/ipsecpolicy.py @@ -35,6 +35,7 @@ class ShowIPsecPolicy(neutronv20.ShowCommand): """Show information of a given IPsec policy.""" resource = 'ipsecpolicy' + help_resource = 'IPsec policy' class CreateIPsecPolicy(neutronv20.CreateCommand): @@ -92,6 +93,7 @@ class UpdateIPsecPolicy(neutronv20.UpdateCommand): """Update a given IPsec policy.""" resource = 'ipsecpolicy' + help_resource = 'IPsec policy' def add_known_arguments(self, parser): parser.add_argument( @@ -113,3 +115,4 @@ class DeleteIPsecPolicy(neutronv20.DeleteCommand): """Delete a given IPsec policy.""" resource = 'ipsecpolicy' + help_resource = 'IPsec policy' diff --git a/neutronclient/neutron/v2_0/vpn/vpnservice.py b/neutronclient/neutron/v2_0/vpn/vpnservice.py index 8bd2198..9e9ae12 100644 --- a/neutronclient/neutron/v2_0/vpn/vpnservice.py +++ b/neutronclient/neutron/v2_0/vpn/vpnservice.py @@ -34,6 +34,7 @@ class ShowVPNService(neutronv20.ShowCommand): """Show information of a given VPN service.""" resource = 'vpnservice' + help_resource = 'VPN service' class CreateVPNService(neutronv20.CreateCommand): @@ -83,9 +84,11 @@ class UpdateVPNService(neutronv20.UpdateCommand): """Update a given VPN service.""" resource = 'vpnservice' + help_resource = 'VPN service' class DeleteVPNService(neutronv20.DeleteCommand): """Delete a given VPN service.""" resource = 'vpnservice' + help_resource = 'VPN service' From b6a3c4aa9abaccb4136db18673a533e11ea0326d Mon Sep 17 00:00:00 2001 From: reedip Date: Fri, 22 Jan 2016 12:10:05 +0900 Subject: [PATCH 21/57] Trivial Update on ReleaseNotes Release notes have been updated, post the merge of [1], stating the fact that now python-neutronclient does not support py33 anymore. [1]: https://review.openstack.org/#/c/257704/ TrivialFix Change-Id: Ic9e0f6083526deb469fc6ed6a3463fbb3f368296 --- releasenotes/notes/relnotes-from-3.0.0-d7306f5af5e3868d.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/releasenotes/notes/relnotes-from-3.0.0-d7306f5af5e3868d.yaml b/releasenotes/notes/relnotes-from-3.0.0-d7306f5af5e3868d.yaml index 15e569f..012bc7f 100644 --- a/releasenotes/notes/relnotes-from-3.0.0-d7306f5af5e3868d.yaml +++ b/releasenotes/notes/relnotes-from-3.0.0-d7306f5af5e3868d.yaml @@ -16,6 +16,7 @@ upgrade: - Cisco-specific neutron client commands have been removed. These commands are ported to networking-cisco. - py26 support has been dropped. + - py33 support has been dropped. fixes: - Name is no longer looked up on RBAC policies, RBAC policies have no name field so the name query to From 11d9cc87e0031ee95f39c2949eb5065c6145458f Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 23 Jan 2016 10:53:38 +0000 Subject: [PATCH 22/57] Updated from global requirements Change-Id: Ib7748eeafe276035ff986f410dff94e4e49ac2cf --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e649fc1..8704380 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,10 +3,10 @@ # process, which may cause wedges in the gate later. pbr>=1.6 # Apache-2.0 cliff>=1.15.0 # Apache-2.0 -debtcollector>=0.3.0 # Apache-2.0 +debtcollector>=1.2.0 # Apache-2.0 iso8601>=0.1.9 # MIT netaddr!=0.7.16,>=0.7.12 # BSD -oslo.i18n>=1.5.0 # Apache-2.0 +oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 oslo.utils>=3.4.0 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 From 65812849419bd13b2f72bcfc8a40ae6c5078427b Mon Sep 17 00:00:00 2001 From: Hirofumi Ichihara Date: Mon, 25 Jan 2016 19:29:57 +0900 Subject: [PATCH 23/57] Fix code-block for python code in doc Change-Id: Ia5cb007b0a0093e478416161763dcaa23b9cfbe4 --- doc/source/usage/library.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/source/usage/library.rst b/doc/source/usage/library.rst index 44ff5a6..dff0696 100644 --- a/doc/source/usage/library.rst +++ b/doc/source/usage/library.rst @@ -54,6 +54,8 @@ Now you can call various methods on the client instance. Alternatively, you can create a client instance using an auth token and a service endpoint URL directly. +.. code-block:: python + >>> from neutronclient.v2_0 import client >>> neutron = client.Client(endpoint_url='http://192.168.206.130:9696/', - token='d3f9226f27774f338019aa2611112ef6') + ... token='d3f9226f27774f338019aa2611112ef6') From 9f792d0b9c6ed86cea0f7efaf65817ff32e5d72d Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Thu, 28 Jan 2016 08:57:09 +0100 Subject: [PATCH 24/57] Update translation setup Follow new infra setup for translations, see spec http://specs.openstack.org/openstack-infra/infra-specs/specs/translation_setup.html for full details. This basically renames python-neutronclient/locale/python-neutronclient.pot to neutronclient/locale/neutronclient. For this we need to update setup.cfg. The domain name is already correct in neutronclient/_i18n.py. There's no need to keep the pot file. The updated scripts work without them. So, we can just delete the file and once there are translations, an updated pot file together with translations can be imported. Change-Id: I888808f8af291223531df799382ad0c70bf8c567 --- .../locale/python-neutronclient.pot | 1768 ----------------- setup.cfg | 12 +- 2 files changed, 6 insertions(+), 1774 deletions(-) delete mode 100644 python-neutronclient/locale/python-neutronclient.pot diff --git a/python-neutronclient/locale/python-neutronclient.pot b/python-neutronclient/locale/python-neutronclient.pot deleted file mode 100644 index 94216dd..0000000 --- a/python-neutronclient/locale/python-neutronclient.pot +++ /dev/null @@ -1,1768 +0,0 @@ -# Translations template for python-neutronclient. -# Copyright (C) 2015 ORGANIZATION -# This file is distributed under the same license as the -# python-neutronclient project. -# FIRST AUTHOR , 2015. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: python-neutronclient 3.1.1.dev64\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-12-03 21:54+0900\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.1.1\n" - -#: neutronclient/client.py:230 -msgid "" -"For \"noauth\" authentication strategy, the endpoint must be specified " -"either in the constructor or using --os-url" -msgstr "" - -#: neutronclient/client.py:241 -#, python-format -msgid "Unknown auth strategy: %s" -msgstr "" - -#: neutronclient/shell.py:136 -#, python-format -msgid "invalid int value: %r" -msgstr "" - -#: neutronclient/shell.py:138 -#, python-format -msgid "input value %d is negative" -msgstr "" - -#: neutronclient/shell.py:410 -#, python-format -msgid "" -"\n" -"Commands for API v%s:\n" -msgstr "" - -#: neutronclient/shell.py:475 -msgid "" -"Increase verbosity of output and show tracebacks on errors. You can " -"repeat this option." -msgstr "" - -#: neutronclient/shell.py:482 -msgid "Suppress output except warnings and errors." -msgstr "" - -#: neutronclient/shell.py:488 -msgid "Show this help message and exit." -msgstr "" - -#: neutronclient/shell.py:494 -msgid "" -"How many times the request to the Neutron server should be retried if it " -"fails." -msgstr "" - -#: neutronclient/shell.py:514 -msgid "Defaults to env[OS_NETWORK_SERVICE_TYPE] or network." -msgstr "" - -#: neutronclient/shell.py:519 -msgid "Defaults to env[OS_ENDPOINT_TYPE] or public." -msgstr "" - -#: neutronclient/shell.py:526 -msgid "DEPRECATED! Use --os-service-type." -msgstr "" - -#: neutronclient/shell.py:533 -msgid "DEPRECATED! Use --os-endpoint-type." -msgstr "" - -#: neutronclient/shell.py:538 -msgid "DEPRECATED! Only keystone is supported." -msgstr "" - -#: neutronclient/shell.py:547 -msgid "Defaults to env[OS_CLOUD]." -msgstr "" - -#: neutronclient/shell.py:552 -msgid "Authentication URL, defaults to env[OS_AUTH_URL]." -msgstr "" - -#: neutronclient/shell.py:561 -msgid "Authentication tenant name, defaults to env[OS_TENANT_NAME]." -msgstr "" - -#: neutronclient/shell.py:580 -msgid "Authentication tenant ID, defaults to env[OS_TENANT_ID]." -msgstr "" - -#: neutronclient/shell.py:594 -msgid "Authentication username, defaults to env[OS_USERNAME]." -msgstr "" - -#: neutronclient/shell.py:602 -msgid "Authentication user ID (Env: OS_USER_ID)" -msgstr "" - -#: neutronclient/shell.py:654 -msgid "" -"Path of certificate file to use in SSL connection. This file can " -"optionally be prepended with the private key. Defaults to env[OS_CERT]." -msgstr "" - -#: neutronclient/shell.py:663 -msgid "" -"Specify a CA bundle file to use in verifying a TLS (https) server " -"certificate. Defaults to env[OS_CACERT]." -msgstr "" - -#: neutronclient/shell.py:671 -msgid "" -"Path of client key to use in SSL connection. This option is not necessary" -" if your key is prepended to your certificate file. Defaults to " -"env[OS_KEY]." -msgstr "" - -#: neutronclient/shell.py:679 -msgid "Authentication password, defaults to env[OS_PASSWORD]." -msgstr "" - -#: neutronclient/shell.py:687 -msgid "Authentication region name, defaults to env[OS_REGION_NAME]." -msgstr "" - -#: neutronclient/shell.py:696 -msgid "Authentication token, defaults to env[OS_TOKEN]." -msgstr "" - -#: neutronclient/shell.py:704 -msgid "" -"Timeout in seconds to wait for an HTTP response. Defaults to " -"env[OS_NETWORK_TIMEOUT] or None if not specified." -msgstr "" - -#: neutronclient/shell.py:710 -msgid "Defaults to env[OS_URL]." -msgstr "" - -#: neutronclient/shell.py:719 -msgid "" -"Explicitly allow neutronclient to perform \"insecure\" SSL (https) " -"requests. The server's certificate will not be verified against any " -"certificate authorities. This option should be used with caution." -msgstr "" - -#: neutronclient/shell.py:821 -#, python-format -msgid "Try 'neutron help %s' for more information." -msgstr "" - -#: neutronclient/common/exceptions.py:39 -msgid "An unknown exception occurred." -msgstr "" - -#: neutronclient/common/exceptions.py:78 -msgid "Unauthorized: bad credentials." -msgstr "" - -#: neutronclient/common/exceptions.py:83 -msgid "Forbidden: your credentials don't give you access to this resource." -msgstr "" - -#: neutronclient/common/exceptions.py:170 -msgid "auth_url was not provided to the Neutron client" -msgstr "" - -#: neutronclient/common/exceptions.py:174 -msgid "Could not find Service or Region in Service Catalog." -msgstr "" - -#: neutronclient/common/exceptions.py:178 -#, python-format -msgid "Could not find endpoint type %(type_)s in Service Catalog." -msgstr "" - -#: neutronclient/common/exceptions.py:182 -msgid "" -"Found more than one matching endpoint in Service Catalog: " -"%(matching_endpoints)" -msgstr "" - -#: neutronclient/common/exceptions.py:195 -#, python-format -msgid "Connection to neutron failed: %(reason)s" -msgstr "" - -#: neutronclient/common/exceptions.py:199 -#, python-format -msgid "SSL certificate validation has failed: %(reason)s" -msgstr "" - -#: neutronclient/common/exceptions.py:203 -#, python-format -msgid "Malformed response body: %(reason)s" -msgstr "" - -#: neutronclient/common/exceptions.py:207 -#, python-format -msgid "Invalid content type %(content_type)s." -msgstr "" - -#: neutronclient/common/exceptions.py:229 -#, python-format -msgid "" -"Multiple %(resource)s matches found for name '%(name)s', use an ID to be " -"more specific." -msgstr "" - -#: neutronclient/common/serializer.py:223 -msgid "Cannot understand JSON" -msgstr "" - -#: neutronclient/common/serializer.py:296 -msgid "Cannot understand XML" -msgstr "" - -#: neutronclient/common/utils.py:56 -#, python-format -msgid "" -"Invalid %(api_name)s client version '%(version)s'. must be one of: " -"%(map_keys)s" -msgstr "" - -#: neutronclient/common/utils.py:113 -#, python-format -msgid "invalid key-value '%s', expected format: key=value" -msgstr "" - -#: neutronclient/common/validators.py:38 -#, python-format -msgid "%(attr_name)s \"%(val)s\" should be an integer [%(min)i:%(max)i]." -msgstr "" - -#: neutronclient/common/validators.py:43 -#, python-format -msgid "" -"%(attr_name)s \"%(val)s\" should be an integer greater than or equal to " -"%(min)i." -msgstr "" - -#: neutronclient/common/validators.py:48 -#, python-format -msgid "" -"%(attr_name)s \"%(val)s\" should be an integer smaller than or equal to " -"%(max)i." -msgstr "" - -#: neutronclient/common/validators.py:53 -#, python-format -msgid "%(attr_name)s \"%(val)s\" should be an integer." -msgstr "" - -#: neutronclient/common/validators.py:68 -#, python-format -msgid "%(attr_name)s \"%(val)s\" is not a valid CIDR." -msgstr "" - -#: neutronclient/neutron/client.py:56 -#, python-format -msgid "API version %s is not supported" -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:69 -#, python-format -msgid "Unable to find %(resource)s with id '%(id)s'" -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:106 -#, python-format -msgid "Unable to find %(resource)s with name '%(name)s'" -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:139 -msgid "Show detailed information." -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:154 -msgid "Specify the field(s) to be returned by server. You can repeat this option." -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:164 -msgid "" -"Specify retrieve unit of each request, then split one request to several " -"requests." -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:174 -msgid "" -"Sorts the list by the specified fields in the specified directions. You " -"can repeat this option, but you must specify an equal number of sort_dir " -"and sort_key values. Extra sort_dir options are ignored. Missing sort_dir" -" options use the default asc value." -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:183 -msgid "Sorts the list in the specified direction. You can repeat this option." -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:209 -#: neutronclient/neutron/v2_0/__init__.py:287 -#: neutronclient/neutron/v2_0/__init__.py:309 -#: neutronclient/neutron/v2_0/__init__.py:314 -#, python-format -msgid "Invalid values_specs %s" -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:279 -#, python-format -msgid "Duplicated options %s" -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:429 -msgid "The XML or JSON request format." -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:478 -#: neutronclient/neutron/v2_0/quota.py:47 -#: neutronclient/neutron/v2_0/quota.py:108 -#: neutronclient/neutron/v2_0/quota.py:152 -msgid "The owner tenant ID." -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:504 -#, python-format -msgid "Created a new %s:" -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:521 -#, python-format -msgid "ID or name of %s to update." -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:523 -#, python-format -msgid "ID of %s to update." -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:545 -#, python-format -msgid "Must specify new values to update %s" -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:561 -#, python-format -msgid "Updated %(resource)s: %(id)s" -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:577 -#, python-format -msgid "ID or name of %s to delete." -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:579 -#, python-format -msgid "ID of %s to delete." -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:607 -#, python-format -msgid "Deleted %(resource)s: %(id)s" -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:732 -#, python-format -msgid "ID or name of %s to look up." -msgstr "" - -#: neutronclient/neutron/v2_0/__init__.py:734 -#, python-format -msgid "ID of %s to look up." -msgstr "" - -#: neutronclient/neutron/v2_0/address_scope.py:45 -msgid "Set the address scope as shared." -msgstr "" - -#: neutronclient/neutron/v2_0/address_scope.py:48 -msgid "Specify the name of the address scope." -msgstr "" - -#: neutronclient/neutron/v2_0/address_scope.py:71 -msgid "Name of the address scope to update." -msgstr "" - -#: neutronclient/neutron/v2_0/agent.py:69 -msgid "Set admin state up of the agent to false." -msgstr "" - -#: neutronclient/neutron/v2_0/agent.py:72 -msgid "Description for the agent." -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:35 -#: neutronclient/neutron/v2_0/agentscheduler.py:60 -#: neutronclient/neutron/v2_0/agentscheduler.py:88 -msgid "ID of the DHCP agent." -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:38 -msgid "Network to add." -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:49 -#, python-format -msgid "Added network %s to DHCP agent" -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:63 -msgid "Network to remove." -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:74 -#, python-format -msgid "Removed network %s from DHCP agent" -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:110 -msgid "Network to query." -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:133 -#: neutronclient/neutron/v2_0/agentscheduler.py:158 -msgid "ID of the L3 agent." -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:136 -msgid "Router to add." -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:147 -#, python-format -msgid "Added router %s to L3 agent" -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:161 -msgid "Router to remove." -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:172 -#, python-format -msgid "Removed router %s from L3 agent" -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:190 -msgid "ID of the L3 agent to query." -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:211 -msgid "Router to query." -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:243 -#: neutronclient/neutron/v2_0/agentscheduler.py:296 -msgid "ID of the loadbalancer agent to query." -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:267 -msgid "Pool to query." -msgstr "" - -#: neutronclient/neutron/v2_0/agentscheduler.py:320 -msgid "LoadBalancer to query." -msgstr "" - -#: neutronclient/neutron/v2_0/floatingip.py:50 -msgid "Network name or ID to allocate floating IP from." -msgstr "" - -#: neutronclient/neutron/v2_0/floatingip.py:53 -msgid "ID of the port to be associated with the floating IP." -msgstr "" - -#: neutronclient/neutron/v2_0/floatingip.py:59 -#: neutronclient/neutron/v2_0/floatingip.py:107 -msgid "IP address on the port (only required if port has multiple IPs)." -msgstr "" - -#: neutronclient/neutron/v2_0/floatingip.py:66 -msgid "IP address of the floating IP" -msgstr "" - -#: neutronclient/neutron/v2_0/floatingip.py:70 -msgid "Subnet ID on which you want to create the floating IP." -msgstr "" - -#: neutronclient/neutron/v2_0/floatingip.py:100 -msgid "ID of the floating IP to associate." -msgstr "" - -#: neutronclient/neutron/v2_0/floatingip.py:103 -msgid "ID or name of the port to be associated with the floating IP." -msgstr "" - -#: neutronclient/neutron/v2_0/floatingip.py:123 -#, python-format -msgid "Associated floating IP %s" -msgstr "" - -#: neutronclient/neutron/v2_0/floatingip.py:138 -msgid "ID of the floating IP to disassociate." -msgstr "" - -#: neutronclient/neutron/v2_0/floatingip.py:147 -#, python-format -msgid "Disassociated floating IP %s" -msgstr "" - -#: neutronclient/neutron/v2_0/metering.py:43 -msgid "Name of metering label to create." -msgstr "" - -#: neutronclient/neutron/v2_0/metering.py:46 -msgid "Description of metering label to create." -msgstr "" - -#: neutronclient/neutron/v2_0/metering.py:50 -msgid "Set the label as shared." -msgstr "" - -#: neutronclient/neutron/v2_0/metering.py:89 -msgid "Id or Name of the label." -msgstr "" - -#: neutronclient/neutron/v2_0/metering.py:92 -#: neutronclient/neutron/v2_0/securitygroup.py:330 -msgid "CIDR to match on." -msgstr "" - -#: neutronclient/neutron/v2_0/metering.py:96 -msgid "Direction of traffic, default: ingress." -msgstr "" - -#: neutronclient/neutron/v2_0/metering.py:100 -msgid "Exclude this CIDR from the label, default: not excluded." -msgstr "" - -#: neutronclient/neutron/v2_0/network.py:114 -#: neutronclient/neutron/v2_0/port.py:230 -#: neutronclient/neutron/v2_0/router.py:62 -#: neutronclient/neutron/v2_0/fw/firewall.py:56 -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:48 -#: neutronclient/neutron/v2_0/lb/member.py:48 -#: neutronclient/neutron/v2_0/lb/pool.py:54 -#: neutronclient/neutron/v2_0/lb/vip.py:52 -#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:49 -#: neutronclient/neutron/v2_0/lb/v2/listener.py:55 -#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:51 -#: neutronclient/neutron/v2_0/lb/v2/pool.py:60 -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:98 -#: neutronclient/neutron/v2_0/vpn/vpnservice.py:47 -msgid "Set admin state up to false." -msgstr "" - -#: neutronclient/neutron/v2_0/network.py:122 -msgid "Set the network as shared." -msgstr "" - -#: neutronclient/neutron/v2_0/network.py:127 -msgid "The physical mechanism by which the virtual network is implemented." -msgstr "" - -#: neutronclient/neutron/v2_0/network.py:132 -msgid "" -"Name of the physical network over which the virtual network is " -"implemented." -msgstr "" - -#: neutronclient/neutron/v2_0/network.py:137 -msgid "VLAN ID for VLAN networks or tunnel-id for GRE/VXLAN networks." -msgstr "" - -#: neutronclient/neutron/v2_0/network.py:143 -msgid "Create a vlan transparent network." -msgstr "" - -#: neutronclient/neutron/v2_0/network.py:146 -msgid "Name of network to create." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:45 -msgid "Name of this port." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:49 -msgid "" -"Desired IP and/or subnet for this port: " -"subnet_id=,ip_address=. You can repeat this option." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:58 -msgid "Device ID of this port." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:64 -msgid "Device owner of this port." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:111 -msgid "ID or name of router to look up." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:135 -msgid "Security group associated with the port. You can repeat this option." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:140 -msgid "Associate no security groups with the port." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:162 -msgid "" -"Extra dhcp options to be assigned to this port: " -"opt_name=,opt_value=,ip_version={4,6}. You can " -"repeat this option." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:173 -msgid "" -"Invalid --extra-dhcp-opt option, can only be: " -"opt_name=,opt_value=,ip_version={4,6}. You can " -"repeat this option." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:204 -msgid "Allowed address pair associated with the port.You can repeat this option." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:209 -msgid "Associate no allowed address pairs with the port." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:237 -msgid "MAC address of this port." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:244 -msgid "VNIC type for this port." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:251 -msgid "Custom data to be passed as binding:profile." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:262 -msgid "Network ID or name this port belongs to." -msgstr "" - -#: neutronclient/neutron/v2_0/port.py:305 -msgid "Set admin state up for the port." -msgstr "" - -#: neutronclient/neutron/v2_0/quota.py:62 -#, python-format -msgid "Deleted %(resource)s: %(tenant_id)s" -msgstr "" - -#: neutronclient/neutron/v2_0/quota.py:158 -msgid "The limit of networks." -msgstr "" - -#: neutronclient/neutron/v2_0/quota.py:161 -msgid "The limit of subnets." -msgstr "" - -#: neutronclient/neutron/v2_0/quota.py:164 -msgid "The limit of ports." -msgstr "" - -#: neutronclient/neutron/v2_0/quota.py:167 -msgid "The limit of routers." -msgstr "" - -#: neutronclient/neutron/v2_0/quota.py:170 -msgid "The limit of floating IPs." -msgstr "" - -#: neutronclient/neutron/v2_0/quota.py:173 -msgid "The limit of security groups." -msgstr "" - -#: neutronclient/neutron/v2_0/quota.py:176 -msgid "The limit of security groups rules." -msgstr "" - -#: neutronclient/neutron/v2_0/quota.py:179 -msgid "The limit of vips." -msgstr "" - -#: neutronclient/neutron/v2_0/quota.py:182 -msgid "The limit of pools." -msgstr "" - -#: neutronclient/neutron/v2_0/quota.py:185 -msgid "The limit of pool members." -msgstr "" - -#: neutronclient/neutron/v2_0/quota.py:188 -msgid "The limit of health monitors." -msgstr "" - -#: neutronclient/neutron/v2_0/quota.py:196 -#, python-format -msgid "Quota limit for %(name)s must be an integer" -msgstr "" - -#: neutronclient/neutron/v2_0/rbac.py:54 -msgid "ID or name of the RBAC object." -msgstr "" - -#: neutronclient/neutron/v2_0/rbac.py:58 -msgid "Type of the object that RBAC policy affects." -msgstr "" - -#: neutronclient/neutron/v2_0/rbac.py:61 neutronclient/neutron/v2_0/rbac.py:91 -msgid "ID of the tenant to which the RBAC policy will be enforced." -msgstr "" - -#: neutronclient/neutron/v2_0/rbac.py:66 -msgid "Action for the RBAC policy." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:69 -msgid "Name of router to create." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:72 -msgid "Create a distributed router." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:75 -msgid "Create a highly available router." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:98 -msgid "Name of this router." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:101 -msgid "Specify the administrative state of the router (True meaning \"Up\")" -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:108 -msgid "True means this router should operate in distributed mode." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:114 -msgid "Route to associate with the router. You can repeat this option." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:119 -msgid "Remove routes associated with the router." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:150 -#: neutronclient/neutron/v2_0/router.py:218 -#: neutronclient/neutron/v2_0/router.py:269 -msgid "ID or name of the router." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:153 -msgid "" -"The format is \"SUBNET|subnet=SUBNET|port=PORT\". Either a subnet or port" -" must be specified. Both ID and name are accepted as SUBNET or PORT. Note" -" that \"subnet=\" can be omitted when specifying a subnet." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:168 -msgid "You must specify either subnet or port for INTERFACE parameter." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:193 -#, python-format -msgid "Added interface %(port)s to router %(router)s." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:205 -#, python-format -msgid "Removed interface from router %s." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:221 -msgid "ID or name of the external network for the gateway." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:224 -msgid "Disable source NAT on the router gateway." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:227 -msgid "" -"Desired IP and/or subnet on external network: " -"subnet_id=,ip_address=. You can repeat this option." -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:255 -#, python-format -msgid "Set gateway for router %s" -msgstr "" - -#: neutronclient/neutron/v2_0/router.py:279 -#, python-format -msgid "Removed gateway from router %s" -msgstr "" - -#: neutronclient/neutron/v2_0/securitygroup.py:126 -#: neutronclient/neutron/v2_0/securitygroup.py:153 -msgid "Name of security group." -msgstr "" - -#: neutronclient/neutron/v2_0/securitygroup.py:129 -#: neutronclient/neutron/v2_0/securitygroup.py:156 -msgid "Description of security group." -msgstr "" - -#: neutronclient/neutron/v2_0/securitygroup.py:189 -msgid "Do not convert security group ID to its name." -msgstr "" - -#: neutronclient/neutron/v2_0/securitygroup.py:305 -msgid "Security group name or ID to add rule." -msgstr "" - -#: neutronclient/neutron/v2_0/securitygroup.py:309 -msgid "Direction of traffic: ingress/egress." -msgstr "" - -#: neutronclient/neutron/v2_0/securitygroup.py:312 -msgid "IPv4/IPv6" -msgstr "" - -#: neutronclient/neutron/v2_0/securitygroup.py:315 -msgid "Protocol of packet." -msgstr "" - -#: neutronclient/neutron/v2_0/securitygroup.py:318 -msgid "Starting port range." -msgstr "" - -#: neutronclient/neutron/v2_0/securitygroup.py:324 -msgid "Ending port range." -msgstr "" - -#: neutronclient/neutron/v2_0/securitygroup.py:336 -msgid "Remote security group name or ID to apply rule." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:54 -msgid "Name of this subnet." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:58 -msgid "Gateway IP of this subnet." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:62 -msgid "No distribution of gateway." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:66 -msgid "" -"Allocation pool IP addresses for this subnet (This option can be " -"repeated)." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:75 -msgid "Additional route (This option can be repeated)." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:79 -msgid "DNS name server for this subnet (This option can be repeated)." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:84 -msgid "Disable DHCP for this subnet." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:88 -msgid "Enable DHCP for this subnet." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:107 -msgid "You cannot enable and disable DHCP at the same time." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:123 -msgid "--ipv6-ra-mode is invalid when --ip-version is 4" -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:128 -msgid "--ipv6-address-mode is invalid when --ip-version is 4" -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:163 -msgid "" -"IP version to use, default is 4. Note that when subnetpool is specified, " -"IP version is determined from the subnetpool and this option is ignored." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:174 -msgid "Network ID or name this subnet belongs to." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:177 -msgid "CIDR of subnet to create." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:181 -msgid "IPv6 RA (Router Advertisement) mode." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:185 -msgid "IPv6 address mode." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:188 -msgid "ID or name of subnetpool from which this subnet will obtain a CIDR." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:192 -msgid "Prefix length for subnet allocation from subnetpool." -msgstr "" - -#: neutronclient/neutron/v2_0/subnet.py:224 -#, python-format -msgid "" -"An IPv%(ip)d subnet with a %(cidr)s CIDR will have only one usable IP " -"address so the device attached to it will not have any IP connectivity." -msgstr "" - -#: neutronclient/neutron/v2_0/subnetpool.py:24 -msgid "Subnetpool minimum prefix length." -msgstr "" - -#: neutronclient/neutron/v2_0/subnetpool.py:27 -msgid "Subnetpool maximum prefix length." -msgstr "" - -#: neutronclient/neutron/v2_0/subnetpool.py:30 -msgid "Subnetpool default prefix length." -msgstr "" - -#: neutronclient/neutron/v2_0/subnetpool.py:34 -msgid "Subnetpool prefixes (This option can be repeated)." -msgstr "" - -#: neutronclient/neutron/v2_0/subnetpool.py:69 -msgid "Set the subnetpool as shared." -msgstr "" - -#: neutronclient/neutron/v2_0/subnetpool.py:72 -msgid "Name of subnetpool to create." -msgstr "" - -#: neutronclient/neutron/v2_0/subnetpool.py:76 -#: neutronclient/neutron/v2_0/subnetpool.py:113 -msgid "" -"ID or name of the address scope with which the subnetpool is associated. " -"Prefixes must be unique across address scopes" -msgstr "" - -#: neutronclient/neutron/v2_0/subnetpool.py:109 -msgid "Name of subnetpool to update." -msgstr "" - -#: neutronclient/neutron/v2_0/subnetpool.py:119 -msgid "Detach subnetpool from the address scope" -msgstr "" - -#: neutronclient/neutron/v2_0/contrib/_fox_sockets.py:24 -#: neutronclient/neutron/v2_0/contrib/_fox_sockets.py:77 -msgid "Name of this fox socket." -msgstr "" - -#: neutronclient/neutron/v2_0/flavor/flavor.py:49 -#: neutronclient/neutron/v2_0/flavor/flavor.py:86 -msgid "Name for the flavor." -msgstr "" - -#: neutronclient/neutron/v2_0/flavor/flavor.py:53 -msgid "" -"Service type to which the flavor applies to: e.g. VPN. (See service-" -"provider-list for loaded examples.)" -msgstr "" - -#: neutronclient/neutron/v2_0/flavor/flavor.py:57 -#: neutronclient/neutron/v2_0/flavor/flavor.py:89 -msgid "Description for the flavor." -msgstr "" - -#: neutronclient/neutron/v2_0/flavor/flavor.py:62 -#: neutronclient/neutron/v2_0/flavor/flavor.py:94 -#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:57 -#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:92 -msgid "Sets enabled flag." -msgstr "" - -#: neutronclient/neutron/v2_0/flavor/flavor.py:113 -msgid "Name or ID of the flavor to associate." -msgstr "" - -#: neutronclient/neutron/v2_0/flavor/flavor.py:117 -msgid "ID of the flavor profile to be associated with the flavor." -msgstr "" - -#: neutronclient/neutron/v2_0/flavor/flavor.py:130 -#, python-format -msgid "Associated flavor %(flavor)s with flavor_profile %(profile)s" -msgstr "" - -#: neutronclient/neutron/v2_0/flavor/flavor.py:147 -msgid "Name or ID of the flavor." -msgstr "" - -#: neutronclient/neutron/v2_0/flavor/flavor.py:151 -msgid "ID of the flavor profile to be disassociated from the flavor." -msgstr "" - -#: neutronclient/neutron/v2_0/flavor/flavor.py:163 -#, python-format -msgid "Disassociated flavor %(flavor)s from flavor_profile %(profile)s" -msgstr "" - -#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:46 -#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:81 -msgid "Description for the flavor profile." -msgstr "" - -#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:49 -#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:84 -msgid "Python module path to driver." -msgstr "" - -#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:52 -#: neutronclient/neutron/v2_0/flavor/flavor_profile.py:87 -msgid "Metainfo for the flavor profile." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewall.py:45 -#: neutronclient/neutron/v2_0/fw/firewall.py:89 -msgid "Firewall policy name or ID." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewall.py:48 -msgid "Name for the firewall." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewall.py:51 -#: neutronclient/neutron/v2_0/fw/firewallrule.py:77 -msgid "Description for the firewall rule." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewall.py:62 -#: neutronclient/neutron/v2_0/fw/firewall.py:96 -msgid "" -"Firewall associated router names or IDs (requires FWaaS router insertion " -"extension, this option can be repeated)" -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewall.py:101 -msgid "" -"Associate no routers with the firewall (requires FWaaS router insertion " -"extension)" -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:37 -msgid "" -"Ordered list of whitespace-delimited firewall rule names or IDs; e.g., " -"--firewall-rules \"rule1 rule2\"" -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:83 -msgid "Name for the firewall policy." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:86 -msgid "Description for the firewall policy." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:91 -msgid "Create a shared policy." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:97 -msgid "Sets audited to True." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:159 -msgid "Insert before this rule." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:163 -msgid "Insert after this rule." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:167 -msgid "New rule to insert." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:179 -#, python-format -msgid "Inserted firewall rule in firewall policy %(id)s" -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:206 -msgid "Firewall rule to remove from policy." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallpolicy.py:218 -#, python-format -msgid "Removed firewall rule from firewall policy %(id)s" -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallrule.py:74 -msgid "Name for the firewall rule." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallrule.py:82 -msgid "Set shared to True (default is False)." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallrule.py:86 -msgid "Source IP address or subnet." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallrule.py:89 -msgid "Destination IP address or subnet." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallrule.py:92 -msgid "Source port (integer in [1, 65535] or range in a:b)." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallrule.py:95 -msgid "Destination port (integer in [1, 65535] or range in a:b)." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallrule.py:99 -msgid "Whether to enable or disable this rule." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallrule.py:103 -#: neutronclient/neutron/v2_0/fw/firewallrule.py:133 -msgid "Protocol for the firewall rule." -msgstr "" - -#: neutronclient/neutron/v2_0/fw/firewallrule.py:108 -msgid "Action for the firewall rule." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:51 -#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:52 -msgid "" -"The list of HTTP status codes expected in response from the member to " -"declare it healthy. This attribute can contain one value, or a list of " -"values separated by comma, or a range of values (e.g. \"200-299\"). If " -"this attribute is not specified, it defaults to \"200\"." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:59 -#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:60 -msgid "The HTTP method used for requests by the monitor of type HTTP." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:63 -#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:64 -msgid "" -"The HTTP path used in the HTTP request used by the monitor to test a " -"member health. This must be a string beginning with a / (forward slash)." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:69 -#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:70 -msgid "The time in seconds between sending probes to members." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:73 -msgid "" -"Number of permissible connection failures before changing the member " -"status to INACTIVE. [1..10]" -msgstr "" - -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:78 -#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:79 -msgid "" -"Maximum number of seconds for a monitor to wait for a connection to be " -"established before it times out. The value must be less than the delay " -"value." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:84 -#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:85 -msgid "One of the predefined health monitor types." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:121 -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:148 -msgid "Health monitor to associate." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:124 -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:151 -msgid "ID of the pool to be associated with the health monitor." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:134 -#, python-format -msgid "Associated health monitor %s" -msgstr "" - -#: neutronclient/neutron/v2_0/lb/healthmonitor.py:162 -#, python-format -msgid "Disassociated health monitor %s" -msgstr "" - -#: neutronclient/neutron/v2_0/lb/member.py:51 -msgid "Weight of pool member in the pool (default:1, [0..256])." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/member.py:55 -msgid "IP address of the pool member on the pool network." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/member.py:59 -#: neutronclient/neutron/v2_0/lb/v2/member.py:90 -msgid "Port on which the pool member listens for requests or connections." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/member.py:63 -#: neutronclient/neutron/v2_0/lb/vip.py:45 -msgid "Pool ID or name this vip belongs to." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/pool.py:57 -#: neutronclient/neutron/v2_0/lb/v2/pool.py:63 -msgid "Description of the pool." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/pool.py:62 -#: neutronclient/neutron/v2_0/lb/v2/pool.py:75 -msgid "The algorithm used to distribute load between the members of the pool." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/pool.py:67 -#: neutronclient/neutron/v2_0/lb/v2/pool.py:70 -msgid "The name of the pool." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/pool.py:72 -#: neutronclient/neutron/v2_0/lb/vip.py:72 -#: neutronclient/neutron/v2_0/lb/v2/pool.py:85 -msgid "Protocol for balancing." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/pool.py:76 -msgid "The subnet on which the members of the pool will be located." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/pool.py:80 -msgid "Provider name of loadbalancer service." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/vip.py:48 -msgid "IP address of the vip." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/vip.py:55 -#: neutronclient/neutron/v2_0/lb/v2/listener.py:58 -msgid "" -"The maximum number of connections per second allowed for the vip. " -"Positive integer or -1 for unlimited (default)." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/vip.py:59 -msgid "Description of the vip." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/vip.py:63 -msgid "Name of the vip." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/vip.py:67 -msgid "" -"TCP port on which to listen for client traffic that is associated with " -"the vip address." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/vip.py:76 -msgid "The subnet on which to allocate the vip address." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:74 -msgid "" -"Number of permissible connection failures before changing the member " -"status to INACTIVE. [1..10]." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/healthmonitor.py:88 -msgid "ID or name of the pool that this healthmonitor will monitor." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/listener.py:62 -msgid "Description of the listener." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/listener.py:65 -msgid "The name of the listener." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/listener.py:69 -msgid "Default TLS container reference to retrieve TLS information." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/listener.py:75 -msgid "List of TLS container references for SNI." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/listener.py:80 -msgid "ID or name of the load balancer." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/listener.py:85 -msgid "Protocol for the listener." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/listener.py:90 -msgid "Protocol port for the listener." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:47 -msgid "Description of the load balancer." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:54 -msgid "Name of the load balancer." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:57 -msgid "Provider name of load balancer service." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:60 -msgid "ID or name of flavor." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:63 -msgid "VIP address for the load balancer." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/loadbalancer.py:66 -msgid "Load balancer VIP subnet." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/member.py:37 -#: neutronclient/neutron/v2_0/lb/v2/member.py:94 -msgid "ID or name of the pool that this member belongs to." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/member.py:75 -#: neutronclient/neutron/v2_0/lb/v2/member.py:119 -msgid "Set admin state up to false" -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/member.py:78 -msgid "Weight of member in the pool (default:1, [0..256])." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/member.py:82 -msgid "Subnet ID or name for the member." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/member.py:86 -msgid "IP address of the pool member in the pool." -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/member.py:122 -msgid "Weight of member in the pool (default:1, [0..256])" -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/member.py:125 -msgid "ID or name of the pool that this member belongs to" -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/pool.py:67 -msgid "The type of session persistence to use and associated cookie name" -msgstr "" - -#: neutronclient/neutron/v2_0/lb/v2/pool.py:80 -msgid "The listener to associate with the pool" -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/networkgateway.py:25 -msgid "" -"Type of the transport zone connector to use for this device. Valid values" -" are gre, stt, ipsec_gre, ipsec_stt, and bridge. Defaults to stt. " -"ipsecgre and ipsecstt are also accepted as alternative names" -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/networkgateway.py:29 -msgid "" -"IP address for this device's transport connector. It must correspond to " -"the IP address of the interface used for tenant traffic on the NSX " -"gateway node." -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/networkgateway.py:32 -msgid "" -"PEM certificate used by the NSX gateway transport node to authenticate " -"with the NSX controller." -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/networkgateway.py:34 -msgid "" -"File containing the PEM certificate used by the NSX gateway transport " -"node to authenticate with the NSX controller." -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/networkgateway.py:170 -msgid "Name of network gateway to create." -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/networkgateway.py:174 -msgid "" -"Device info for this gateway. You can repeat this option for multiple " -"devices for HA gateways." -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/networkgateway.py:212 -msgid "ID of the network gateway." -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/networkgateway.py:215 -msgid "ID of the internal network to connect on the gateway." -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/networkgateway.py:218 -msgid "" -"L2 segmentation strategy on the external side of the gateway (e.g.: VLAN," -" FLAT)." -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/networkgateway.py:222 -msgid "Identifier for the L2 segment on the external side of the gateway." -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/networkgateway.py:249 -#, python-format -msgid "Connected network to gateway %s" -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/networkgateway.py:268 -#, python-format -msgid "Disconnected network from gateway %s" -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/qos_queue.py:43 -msgid "Name of queue." -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/qos_queue.py:46 -msgid "Minimum rate." -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/qos_queue.py:49 -msgid "Maximum rate." -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/qos_queue.py:52 -msgid "QOS marking as untrusted or trusted." -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/qos_queue.py:56 -msgid "" -"If true all created ports will be the size of this queue, if queue is not" -" specified" -msgstr "" - -#: neutronclient/neutron/v2_0/nsx/qos_queue.py:60 -msgid "Differentiated Services Code Point." -msgstr "" - -#: neutronclient/neutron/v2_0/qos/bandwidth_limit_rule.py:30 -msgid "max bandwidth in kbps." -msgstr "" - -#: neutronclient/neutron/v2_0/qos/bandwidth_limit_rule.py:33 -msgid "max burst bandwidth in kbps." -msgstr "" - -#: neutronclient/neutron/v2_0/qos/bandwidth_limit_rule.py:40 -msgid "Must provide max_kbps or max_burst_kbps option." -msgstr "" - -#: neutronclient/neutron/v2_0/qos/policy.py:34 -msgid "Attach QoS policy ID or name to the resource." -msgstr "" - -#: neutronclient/neutron/v2_0/qos/policy.py:51 -msgid "Detach QoS policy from the resource." -msgstr "" - -#: neutronclient/neutron/v2_0/qos/policy.py:95 -msgid "Name of QoS policy to create." -msgstr "" - -#: neutronclient/neutron/v2_0/qos/policy.py:98 -#: neutronclient/neutron/v2_0/qos/policy.py:128 -msgid "Description of the QoS policy." -msgstr "" - -#: neutronclient/neutron/v2_0/qos/policy.py:102 -#: neutronclient/neutron/v2_0/qos/policy.py:132 -msgid "Accessible by other tenants. Set shared to True (default is False)." -msgstr "" - -#: neutronclient/neutron/v2_0/qos/policy.py:125 -msgid "Name of QoS policy." -msgstr "" - -#: neutronclient/neutron/v2_0/qos/rule.py:26 -msgid "ID or name of the QoS policy." -msgstr "" - -#: neutronclient/neutron/v2_0/qos/rule.py:32 -msgid "ID of the QoS rule." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/endpoint_group.py:24 -msgid "Set a name for the endpoint group." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/endpoint_group.py:27 -msgid "Set a description for the endpoint group." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/endpoint_group.py:32 -msgid "Type of endpoints in group (e.g. subnet, cidr, vlan)." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/endpoint_group.py:37 -msgid "Endpoint(s) for the group. Must all be of the same type." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:48 -msgid "Description of the IKE policy" -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:52 -msgid "Authentication algorithm in lowercase. Default:sha1" -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:57 -#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:60 -msgid "Encryption algorithm in lowercase, default:aes-128" -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:61 -msgid "IKE Phase1 negotiation mode in lowercase, default:main" -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:65 -msgid "IKE version in lowercase, default:v1" -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:69 -#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:68 -msgid "Perfect Forward Secrecy in lowercase, default:group5" -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ikepolicy.py:77 -msgid "Name of the IKE policy." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:61 -msgid "Local endpoint group ID/name with subnet(s) for IPSec connection." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:65 -msgid "Peer endpoint group ID/name with CIDR(s) for IPsec connection." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:101 -msgid "Set friendly name for the connection." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:104 -msgid "Set a description for the connection." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:108 -msgid "MTU size for the connection, default:1500" -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:113 -msgid "Initiator state in lowercase, default:bi-directional" -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:117 -msgid "VPN service instance ID associated with this connection." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:121 -msgid "IKE policy ID associated with this connection." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:125 -msgid "IPsec policy ID associated with this connection." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:129 -msgid "Peer gateway public IPv4/IPv6 address or FQDN." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:133 -msgid "" -"Peer router identity for authentication. Can be IPv4/IPv6 address, e-mail" -" address, key id, or FQDN." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:138 -msgid "" -"[DEPRECATED in Mitaka] Remote subnet(s) in CIDR format. Cannot be " -"specified when using endpoint groups. Only applicable, if subnet provided" -" for VPN service." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:144 -msgid "Pre-shared key string." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:158 -msgid "Invalid MTU value: MTU must be greater than or equal to 68" -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:163 -msgid "You must specify both local and peer endpoint groups." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:167 -msgid "You cannot specify both endpoint groups and peer CIDR(s)." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py:171 -msgid "You must specify endpoint groups or peer CIDR(s)." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:48 -msgid "Description of the IPsec policy." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:52 -msgid "Transform protocol in lowercase, default:esp" -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:56 -msgid "Authentication algorithm in lowercase, default:sha1" -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:64 -msgid "Encapsulation mode in lowercase, default:tunnel" -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/ipsecpolicy.py:76 -msgid "Name of the IPsec policy." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/utils.py:35 -#, python-format -msgid "" -"DPD Dictionary KeyError: Reason-Invalid DPD key : '%(key)s' not in " -"%(supported_key)s " -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/utils.py:42 -#, python-format -msgid "" -"DPD Dictionary ValueError: Reason-Invalid DPD action : '%(key_value)s' " -"not in %(supported_action)s " -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/utils.py:54 -#, python-format -msgid "" -"DPD Dictionary ValueError: Reason-Invalid positive integer value: " -"'%(key)s' = %(value)s " -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/utils.py:69 -#, python-format -msgid "" -"Lifetime Dictionary KeyError: Reason-Invalid unit key : '%(key)s' not in " -"%(supported_key)s " -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/utils.py:76 -#, python-format -msgid "" -"Lifetime Dictionary ValueError: Reason-Invalid units : '%(key_value)s' " -"not in %(supported_units)s " -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/utils.py:87 -#, python-format -msgid "" -"Lifetime Dictionary ValueError: Reason-Invalid value should be at least " -"60:'%(key_value)s' = %(value)s " -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/utils.py:99 -#, python-format -msgid "" -"%s lifetime attributes. 'units'-seconds, default:seconds. 'value'-non " -"negative integer, default:3600." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/utils.py:106 -#, python-format -msgid "" -" %s Dead Peer Detection attributes. 'action'-hold,clear,disabled,restart" -",restart-by-peer. 'interval' and 'timeout' are non negative integers. " -"'interval' should be less than 'timeout' value. 'action', default:hold " -"'interval', default:30, 'timeout', default:120." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/vpnservice.py:50 -msgid "Set a name for the VPN service." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/vpnservice.py:53 -msgid "Set a description for the VPN service." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/vpnservice.py:56 -msgid "Router unique identifier for the VPN service." -msgstr "" - -#: neutronclient/neutron/v2_0/vpn/vpnservice.py:59 -msgid "[DEPRECATED in Mitaka] Unique identifier for the local private subnet." -msgstr "" - -#: neutronclient/v2_0/client.py:228 -#, python-format -msgid "Unable to serialize object of type = '%s'" -msgstr "" - -#: neutronclient/v2_0/client.py:280 -#, python-format -msgid "Failed to connect to Neutron server after %d attempts" -msgstr "" - -#: neutronclient/v2_0/client.py:283 -msgid "Failed to connect Neutron server" -msgstr "" - diff --git a/setup.cfg b/setup.cfg index b5724fd..9dec54c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,13 +42,13 @@ universal = 1 [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg -output_file = python-neutronclient/locale/python-neutronclient.pot +output_file = neutronclient/locale/neutronclient.pot [compile_catalog] -directory = python-neutronclient/locale -domain = python-neutronclient +directory = neutronclient/locale +domain = neutronclient [update_catalog] -domain = python-neutronclient -output_dir = python-neutronclient/locale -input_file = python-neutronclient/locale/python-neutronclient.pot +domain = neutronclient +output_dir = neutronclient/locale +input_file = neutronclient/locale/neutronclient.pot From 809af6c080a87f668a0d1855f0edb3d528e33a30 Mon Sep 17 00:00:00 2001 From: James Arendt Date: Sat, 30 Jan 2016 21:08:58 -0800 Subject: [PATCH 25/57] Make metavar usage consistent Neutron CLI generally uses all caps for positional arguments via metavars. There are a few exceptions in the current code that do not match the other interfaces. Change-Id: I905baebb3428e994cb724031cbb92120a0953d71 Closes-Bug: #1541625 --- neutronclient/neutron/v2_0/address_scope.py | 1 + neutronclient/neutron/v2_0/agentscheduler.py | 16 ++++++++++++++++ neutronclient/neutron/v2_0/port.py | 2 +- neutronclient/neutron/v2_0/subnetpool.py | 1 + 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/neutronclient/neutron/v2_0/address_scope.py b/neutronclient/neutron/v2_0/address_scope.py index 8a89282..662209b 100755 --- a/neutronclient/neutron/v2_0/address_scope.py +++ b/neutronclient/neutron/v2_0/address_scope.py @@ -45,6 +45,7 @@ class CreateAddressScope(neutronV20.CreateCommand): help=_('Set the address scope as shared.')) parser.add_argument( 'name', + metavar='NAME', help=_('Specify the name of the address scope.')) parser.add_argument( 'ip_version', diff --git a/neutronclient/neutron/v2_0/agentscheduler.py b/neutronclient/neutron/v2_0/agentscheduler.py index 3348e64..24287b7 100644 --- a/neutronclient/neutron/v2_0/agentscheduler.py +++ b/neutronclient/neutron/v2_0/agentscheduler.py @@ -32,9 +32,11 @@ class AddNetworkToDhcpAgent(neutronV20.NeutronCommand): parser = super(AddNetworkToDhcpAgent, self).get_parser(prog_name) parser.add_argument( 'dhcp_agent', + metavar='DHCP_AGENT', help=_('ID of the DHCP agent.')) parser.add_argument( 'network', + metavar='NETWORK', help=_('Network to add.')) return parser @@ -56,9 +58,11 @@ class RemoveNetworkFromDhcpAgent(neutronV20.NeutronCommand): parser = super(RemoveNetworkFromDhcpAgent, self).get_parser(prog_name) parser.add_argument( 'dhcp_agent', + metavar='DHCP_AGENT', help=_('ID of the DHCP agent.')) parser.add_argument( 'network', + metavar='NETWORK', help=_('Network to remove.')) return parser @@ -83,6 +87,7 @@ class ListNetworksOnDhcpAgent(network.ListNetwork): self).get_parser(prog_name) parser.add_argument( 'dhcp_agent', + metavar='DHCP_AGENT', help=_('ID of the DHCP agent.')) return parser @@ -105,6 +110,7 @@ class ListDhcpAgentsHostingNetwork(neutronV20.ListCommand): self).get_parser(prog_name) parser.add_argument( 'network', + metavar='NETWORK', help=_('Network to query.')) return parser @@ -128,9 +134,11 @@ class AddRouterToL3Agent(neutronV20.NeutronCommand): parser = super(AddRouterToL3Agent, self).get_parser(prog_name) parser.add_argument( 'l3_agent', + metavar='L3_AGENT', help=_('ID of the L3 agent.')) parser.add_argument( 'router', + metavar='ROUTER', help=_('Router to add.')) return parser @@ -152,9 +160,11 @@ class RemoveRouterFromL3Agent(neutronV20.NeutronCommand): parser = super(RemoveRouterFromL3Agent, self).get_parser(prog_name) parser.add_argument( 'l3_agent', + metavar='L3_AGENT', help=_('ID of the L3 agent.')) parser.add_argument( 'router', + metavar='ROUTER', help=_('Router to remove.')) return parser @@ -183,6 +193,7 @@ class ListRoutersOnL3Agent(neutronV20.ListCommand): self).get_parser(prog_name) parser.add_argument( 'l3_agent', + metavar='L3_AGENT', help=_('ID of the L3 agent to query.')) return parser @@ -204,6 +215,7 @@ class ListL3AgentsHostingRouter(neutronV20.ListCommand): parser = super(ListL3AgentsHostingRouter, self).get_parser(prog_name) parser.add_argument('router', + metavar='ROUTER', help=_('Router to query.')) return parser @@ -236,6 +248,7 @@ class ListPoolsOnLbaasAgent(neutronV20.ListCommand): parser = super(ListPoolsOnLbaasAgent, self).get_parser(prog_name) parser.add_argument( 'lbaas_agent', + metavar='LBAAS_AGENT', help=_('ID of the loadbalancer agent to query.')) return parser @@ -260,6 +273,7 @@ class GetLbaasAgentHostingPool(neutronV20.ListCommand): parser = super(GetLbaasAgentHostingPool, self).get_parser(prog_name) parser.add_argument('pool', + metavar='POOL', help=_('Pool to query.')) return parser @@ -289,6 +303,7 @@ class ListLoadBalancersOnLbaasAgent(neutronV20.ListCommand): prog_name) parser.add_argument( 'lbaas_agent', + metavar='LBAAS_AGENT', help=_('ID of the loadbalancer agent to query.')) return parser @@ -313,6 +328,7 @@ class GetLbaasAgentHostingLoadBalancer(neutronV20.ListCommand): parser = super(GetLbaasAgentHostingLoadBalancer, self).get_parser(prog_name) parser.add_argument('loadbalancer', + metavar='LOADBALANCER', help=_('LoadBalancer to query.')) return parser diff --git a/neutronclient/neutron/v2_0/port.py b/neutronclient/neutron/v2_0/port.py index 6c997f4..0e27cd2 100644 --- a/neutronclient/neutron/v2_0/port.py +++ b/neutronclient/neutron/v2_0/port.py @@ -107,7 +107,7 @@ class ListRouterPort(neutronV20.ListCommand): def get_parser(self, prog_name): parser = super(ListRouterPort, self).get_parser(prog_name) parser.add_argument( - 'id', metavar='router', + 'id', metavar='ROUTER', help=_('ID or name of router to look up.')) return parser diff --git a/neutronclient/neutron/v2_0/subnetpool.py b/neutronclient/neutron/v2_0/subnetpool.py index 3b36bfe..a4a2247 100644 --- a/neutronclient/neutron/v2_0/subnetpool.py +++ b/neutronclient/neutron/v2_0/subnetpool.py @@ -82,6 +82,7 @@ class CreateSubnetPool(neutronV20.CreateCommand): help=_('Set the subnetpool as shared.')) parser.add_argument( 'name', + metavar='NAME', help=_('Name of subnetpool to create.')) parser.add_argument( '--address-scope', From d35643974c28f900b4f976f728b9bd43f95eeba7 Mon Sep 17 00:00:00 2001 From: reedip Date: Mon, 14 Dec 2015 09:57:52 +0900 Subject: [PATCH 26/57] Allow UPPER case in protocol/action for FW Rule Currently firewall rule create/update allows only lower case values for its protocol and action arguments. Limiting the protocol/action attribute to lower case is not very user friendly. This patch allows the user to provide protocol and action fields in UPPER/lower case. Change-Id: Ib8b278fc89f81d89d30f4e8dde9797e9149d3919 Co-Authored-By:Akihiro Motoki Closes-Bug: #1508753 --- neutronclient/neutron/v2_0/fw/firewallrule.py | 7 ++++++- .../tests/unit/fw/test_cli20_firewallrule.py | 20 ++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/neutronclient/neutron/v2_0/fw/firewallrule.py b/neutronclient/neutron/v2_0/fw/firewallrule.py index e77e96f..70ef8e8 100644 --- a/neutronclient/neutron/v2_0/fw/firewallrule.py +++ b/neutronclient/neutron/v2_0/fw/firewallrule.py @@ -103,18 +103,20 @@ class CreateFirewallRule(neutronv20.CreateCommand): help=_('Whether to enable or disable this rule.')) parser.add_argument( '--protocol', choices=['tcp', 'udp', 'icmp', 'any'], + type=utils.convert_to_lowercase, required=True, help=_('Protocol for the firewall rule.')) parser.add_argument( '--action', required=True, + type=utils.convert_to_lowercase, choices=['allow', 'deny', 'reject'], help=_('Action for the firewall rule.')) def args2body(self, parsed_args): body = {} neutronv20.update_dict(parsed_args, body, - ['name', 'description', 'shared', 'protocol', + ['name', 'description', 'shared', 'source_ip_address', 'destination_ip_address', 'source_port', 'destination_port', 'action', 'enabled', 'tenant_id', @@ -135,7 +137,10 @@ class UpdateFirewallRule(neutronv20.UpdateCommand): parser.add_argument( '--protocol', choices=['tcp', 'udp', 'icmp', 'any'], required=False, + type=utils.convert_to_lowercase, help=_('Protocol for the firewall rule.')) + # TODO(reedip) : Need to add the option for action once + # action also comes into Update Firewall Rule def args2body(self, parsed_args): body = {} diff --git a/neutronclient/tests/unit/fw/test_cli20_firewallrule.py b/neutronclient/tests/unit/fw/test_cli20_firewallrule.py index 9c22035..d84f9ed 100644 --- a/neutronclient/tests/unit/fw/test_cli20_firewallrule.py +++ b/neutronclient/tests/unit/fw/test_cli20_firewallrule.py @@ -58,8 +58,9 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base): def test_create_disabled_firewall_rule_with_mandatory_params(self): self._test_create_firewall_rule_with_mandatory_params(enabled='False') - def _setup_create_firewall_rule_with_all_params(self, protocol='tcp', - ip_version='4'): + def _setup_create_firewall_rule_with_all_params( + self, protocol='tcp', protocol_cli=None, + action='allow', action_cli=None, ip_version='4'): # firewall-rule-create with all params set. resource = 'firewall_rule' cmd = firewallrule.CreateFirewallRule(test_cli20.MyApp(sys.stdout), @@ -70,19 +71,18 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base): destination_ip = '192.168.2.0/24' source_port = '0:65535' destination_port = '0:65535' - action = 'allow' tenant_id = 'my-tenant' my_id = 'myid' enabled = 'True' args = ['--description', description, '--shared', - '--protocol', protocol, + '--protocol', protocol_cli or protocol, '--ip-version', ip_version, '--source-ip-address', source_ip, '--destination-ip-address', destination_ip, '--source-port', source_port, '--destination-port', destination_port, - '--action', action, + '--action', action_cli or action, '--enabled', enabled, '--admin-state-up', '--tenant-id', tenant_id] @@ -126,6 +126,16 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base): def test_create_firewall_rule_with_invalid_IP_version(self): self._setup_create_firewall_rule_with_all_params(ip_version='5') + def test_create_firewall_rule_with_proto_action_upper_capitalized(self): + for protocol in ('TCP', 'Tcp', 'ANY', 'AnY'): + self._setup_create_firewall_rule_with_all_params( + protocol=protocol.lower(), + protocol_cli=protocol) + for action in ('Allow', 'DENY', 'reject'): + self._setup_create_firewall_rule_with_all_params( + action=action.lower(), + action_cli=action) + def test_list_firewall_rules(self): # firewall-rule-list. resources = "firewall_rules" From eafefadeefa3e5ea1d0f41a8af85017505a0d1bd Mon Sep 17 00:00:00 2001 From: Reedip Banerjee Date: Thu, 10 Dec 2015 10:22:19 +0530 Subject: [PATCH 27/57] Show all updatable options in (fw/fw-policy)-update CLI Currently firewall-policy-update and firewall-update CLIs do not show all the options which can be updated in its help section. This patch adds the following updatable options # Firewall-Policy: - shared - audited - description - name # Firewall: - admin-state - description - name This patch adds the information to the above FW CLIs. Change-Id: I66a80ff68fc369298528cd59923c294ed39f4e80 Closes-Bug: #1504411 --- neutronclient/neutron/v2_0/fw/firewall.py | 109 +++++++++--------- .../neutron/v2_0/fw/firewallpolicy.py | 31 +++-- .../tests/unit/fw/test_cli20_firewall.py | 8 ++ .../unit/fw/test_cli20_firewallpolicy.py | 11 ++ 4 files changed, 95 insertions(+), 64 deletions(-) diff --git a/neutronclient/neutron/v2_0/fw/firewall.py b/neutronclient/neutron/v2_0/fw/firewall.py index dacca9f..b7d3ea8 100644 --- a/neutronclient/neutron/v2_0/fw/firewall.py +++ b/neutronclient/neutron/v2_0/fw/firewall.py @@ -13,11 +13,52 @@ # License for the specific language governing permissions and limitations # under the License. # - from neutronclient._i18n import _ +from neutronclient.common import utils from neutronclient.neutron import v2_0 as neutronv20 +def add_common_args(parser): + parser.add_argument( + '--name', + help=_('Name for the firewall.')) + parser.add_argument( + '--description', + help=_('Description for the firewall.')) + router = parser.add_mutually_exclusive_group() + router.add_argument( + '--router', + dest='routers', + metavar='ROUTER', + action='append', + help=_('Firewall associated router name or ID (requires FWaaS ' + 'router insertion extension, this option can be repeated)')) + router.add_argument( + '--no-routers', + action='store_true', + help=_('Associate no routers with the firewall (requires FWaaS ' + 'router insertion extension)')) + + +def parse_common_args(client, parsed_args): + body = {} + if parsed_args.policy: + body['firewall_policy_id'] = neutronv20.find_resourceid_by_name_or_id( + client, 'firewall_policy', + parsed_args.policy) + + if parsed_args.routers: + body['router_ids'] = [ + neutronv20.find_resourceid_by_name_or_id(client, 'router', r) + for r in parsed_args.routers] + elif parsed_args.no_routers: + body['router_ids'] = [] + + neutronv20.update_dict(parsed_args, body, + ['name', 'description']) + return body + + class ListFirewall(neutronv20.ListCommand): """List firewalls that belong to a given tenant.""" @@ -40,41 +81,20 @@ class CreateFirewall(neutronv20.CreateCommand): resource = 'firewall' def add_known_arguments(self, parser): + add_common_args(parser) parser.add_argument( - 'firewall_policy_id', metavar='POLICY', + 'policy', metavar='POLICY', help=_('Firewall policy name or ID.')) - parser.add_argument( - '--name', - help=_('Name for the firewall.')) - parser.add_argument( - '--description', - help=_('Description for the firewall rule.')) parser.add_argument( '--admin-state-down', dest='admin_state', action='store_false', help=_('Set admin state up to false.')) - parser.add_argument( - '--router', - dest='routers', - metavar='ROUTER', - action='append', - help=_('Firewall associated router names or IDs (requires FWaaS ' - 'router insertion extension, this option can be repeated)')) def args2body(self, parsed_args): - client = self.get_client() - _policy_id = neutronv20.find_resourceid_by_name_or_id( - client, 'firewall_policy', - parsed_args.firewall_policy_id) - body = {'firewall_policy_id': _policy_id, - 'admin_state_up': parsed_args.admin_state, } - if parsed_args.routers: - body['router_ids'] = [ - neutronv20.find_resourceid_by_name_or_id(client, 'router', r) - for r in parsed_args.routers] - neutronv20.update_dict(parsed_args, body, - ['name', 'description', 'tenant_id']) + body = parse_common_args(self.get_client(), parsed_args) + neutronv20.update_dict(parsed_args, body, ['tenant_id']) + body['admin_state_up'] = parsed_args.admin_state return {self.resource: body} @@ -84,38 +104,19 @@ class UpdateFirewall(neutronv20.UpdateCommand): resource = 'firewall' def add_known_arguments(self, parser): + add_common_args(parser) parser.add_argument( '--policy', metavar='POLICY', help=_('Firewall policy name or ID.')) - router_sg = parser.add_mutually_exclusive_group() - router_sg.add_argument( - '--router', - dest='routers', - metavar='ROUTER', - action='append', - help=_('Firewall associated router names or IDs (requires FWaaS ' - 'router insertion extension, this option can be repeated)')) - router_sg.add_argument( - '--no-routers', - action='store_true', - help=_('Associate no routers with the firewall (requires FWaaS ' - 'router insertion extension)')) + utils.add_boolean_argument( + parser, '--admin-state-up', dest='admin_state_up', + help=_('Update the admin state for the firewall' + '(True means UP)')) def args2body(self, parsed_args): - data = {} - client = self.get_client() - if parsed_args.policy: - _policy_id = neutronv20.find_resourceid_by_name_or_id( - client, 'firewall_policy', - parsed_args.policy) - data['firewall_policy_id'] = _policy_id - if parsed_args.routers: - data['router_ids'] = [ - neutronv20.find_resourceid_by_name_or_id(client, 'router', r) - for r in parsed_args.routers] - elif parsed_args.no_routers: - data['router_ids'] = [] - return {self.resource: data} + body = parse_common_args(self.get_client(), parsed_args) + neutronv20.update_dict(parsed_args, body, ['admin_state_up']) + return {self.resource: body} class DeleteFirewall(neutronv20.DeleteCommand): diff --git a/neutronclient/neutron/v2_0/fw/firewallpolicy.py b/neutronclient/neutron/v2_0/fw/firewallpolicy.py index 0778687..08d623a 100644 --- a/neutronclient/neutron/v2_0/fw/firewallpolicy.py +++ b/neutronclient/neutron/v2_0/fw/firewallpolicy.py @@ -19,6 +19,7 @@ from __future__ import print_function import argparse from neutronclient._i18n import _ +from neutronclient.common import utils from neutronclient.neutron import v2_0 as neutronv20 @@ -31,14 +32,17 @@ def _format_firewall_rules(firewall_policy): return '' -def common_add_known_arguments(parser): +def add_common_args(parser): + parser.add_argument( + '--description', + help=_('Description for the firewall policy.')) parser.add_argument( '--firewall-rules', type=lambda x: x.split(), help=_('Ordered list of whitespace-delimited firewall rule ' 'names or IDs; e.g., --firewall-rules \"rule1 rule2\"')) -def common_args2body(client, parsed_args): +def parse_common_args(client, parsed_args): if parsed_args.firewall_rules: _firewall_rules = [] for f in parsed_args.firewall_rules: @@ -81,24 +85,20 @@ class CreateFirewallPolicy(neutronv20.CreateCommand): 'name', metavar='NAME', help=_('Name for the firewall policy.')) - parser.add_argument( - '--description', - help=_('Description for the firewall policy.')) parser.add_argument( '--shared', - dest='shared', action='store_true', help=_('Create a shared policy.'), default=argparse.SUPPRESS) - common_add_known_arguments(parser) parser.add_argument( '--audited', action='store_true', help=_('Sets audited to True.'), default=argparse.SUPPRESS) + add_common_args(parser) def args2body(self, parsed_args): - return common_args2body(self.get_client(), parsed_args) + return parse_common_args(self.get_client(), parsed_args) class UpdateFirewallPolicy(neutronv20.UpdateCommand): @@ -107,10 +107,21 @@ class UpdateFirewallPolicy(neutronv20.UpdateCommand): resource = 'firewall_policy' def add_known_arguments(self, parser): - common_add_known_arguments(parser) + add_common_args(parser) + parser.add_argument( + '--name', + help=_('Name for the firewall policy.')) + utils.add_boolean_argument( + parser, '--shared', + help=_('Update the sharing status of the policy. ' + '(True means shared)')) + utils.add_boolean_argument( + parser, '--audited', + help=_('Update the audit status of the policy. ' + '(True means auditing is enabled)')) def args2body(self, parsed_args): - return common_args2body(self.get_client(), parsed_args) + return parse_common_args(self.get_client(), parsed_args) class DeleteFirewallPolicy(neutronv20.DeleteCommand): diff --git a/neutronclient/tests/unit/fw/test_cli20_firewall.py b/neutronclient/tests/unit/fw/test_cli20_firewall.py index ece9ac8..f9a2dee 100644 --- a/neutronclient/tests/unit/fw/test_cli20_firewall.py +++ b/neutronclient/tests/unit/fw/test_cli20_firewall.py @@ -160,3 +160,11 @@ class CLITestV20FirewallJSON(test_cli20.CLITestV20Base): my_id = 'my-id' args = [my_id] self._test_delete_resource(resource, cmd, my_id, args) + + def test_update_firewall_admin_state(self): + # firewall-update myid --admin-state-up True. + resource = 'firewall' + cmd = firewall.UpdateFirewall(test_cli20.MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--admin-state-up', 'True'], + {'admin_state_up': 'True'}) diff --git a/neutronclient/tests/unit/fw/test_cli20_firewallpolicy.py b/neutronclient/tests/unit/fw/test_cli20_firewallpolicy.py index 3d3241e..8ec611c 100644 --- a/neutronclient/tests/unit/fw/test_cli20_firewallpolicy.py +++ b/neutronclient/tests/unit/fw/test_cli20_firewallpolicy.py @@ -213,3 +213,14 @@ class CLITestV20FirewallPolicyJSON(test_cli20.CLITestV20Base): shell.run_command(cmd, cmd_parser, args) self.mox.VerifyAll() self.mox.UnsetStubs() + + def test_update_firewall_policy_name_shared_audited(self): + # firewall-policy-update myid --name newname2 --shared --audited + resource = 'firewall_policy' + cmd = firewallpolicy.UpdateFirewallPolicy(test_cli20.MyApp(sys.stdout), + None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', 'newname2', + '--shared', 'True', '--audited', 'True'], + {'name': 'newname2', + 'shared': 'True', 'audited': 'True'}) From 4ac0d8068c60f7568579f87518ccfc58064e4973 Mon Sep 17 00:00:00 2001 From: reedip Date: Tue, 15 Dec 2015 14:57:27 +0900 Subject: [PATCH 28/57] "neutron help firewall-rule-update" info updated Currently, neutron firewall-rule-update does not show all the options which it supports. With this patch, additional options which were earlier missing are added to the help option of firewall-rule-update. The additional options added are: --name --description --shared --source-ip-address --destination-ip-address --source-port --destination-port --enabled --action --ip-version Change-Id: I000dacfe9acbd220a61b2d3c6cea86c7a42b398f Depends-On: Ib8b278fc89f81d89d30f4e8dde9797e9149d3919 Closes-Bug: #1503985 --- neutronclient/neutron/v2_0/fw/firewallrule.py | 130 ++++++++++-------- .../tests/unit/fw/test_cli20_firewallrule.py | 47 ++++++- 2 files changed, 113 insertions(+), 64 deletions(-) diff --git a/neutronclient/neutron/v2_0/fw/firewallrule.py b/neutronclient/neutron/v2_0/fw/firewallrule.py index 70ef8e8..df5db95 100644 --- a/neutronclient/neutron/v2_0/fw/firewallrule.py +++ b/neutronclient/neutron/v2_0/fw/firewallrule.py @@ -21,6 +21,62 @@ from neutronclient.common import utils from neutronclient.neutron import v2_0 as neutronv20 +def _add_common_args(parser, is_create=True): + """If is_create is True, protocol and action become mandatory arguments. + + CreateCommand = is_create : True + UpdateCommand = is_create : False + """ + parser.add_argument( + '--name', + help=_('Name for the firewall rule.')) + parser.add_argument( + '--description', + help=_('Description for the firewall rule.')) + parser.add_argument( + '--source-ip-address', + help=_('Source IP address or subnet.')) + parser.add_argument( + '--destination-ip-address', + help=_('Destination IP address or subnet.')) + parser.add_argument( + '--source-port', + help=_('Source port (integer in [1, 65535] or range in a:b).')) + parser.add_argument( + '--destination-port', + help=_('Destination port (integer in [1, 65535] or range in ' + 'a:b).')) + utils.add_boolean_argument( + parser, '--enabled', dest='enabled', + help=_('Whether to enable or disable this rule.')) + parser.add_argument( + '--protocol', choices=['tcp', 'udp', 'icmp', 'any'], + required=is_create, + type=utils.convert_to_lowercase, + help=_('Protocol for the firewall rule.')) + parser.add_argument( + '--action', + required=is_create, + type=utils.convert_to_lowercase, + choices=['allow', 'deny', 'reject'], + help=_('Action for the firewall rule.')) + + +def common_args2body(parsed_args): + body = {} + neutronv20.update_dict(parsed_args, body, + ['name', 'description', 'shared', 'tenant_id', + 'source_ip_address', 'destination_ip_address', + 'source_port', 'destination_port', 'action', + 'enabled', 'ip_version']) + protocol = parsed_args.protocol + if protocol: + if protocol == 'any': + protocol = None + body['protocol'] = protocol + return body + + class ListFirewallRule(neutronv20.ListCommand): """List firewall rules that belong to a given tenant.""" @@ -69,63 +125,19 @@ class CreateFirewallRule(neutronv20.CreateCommand): resource = 'firewall_rule' def add_known_arguments(self, parser): - parser.add_argument( - '--name', - help=_('Name for the firewall rule.')) - parser.add_argument( - '--description', - help=_('Description for the firewall rule.')) parser.add_argument( '--shared', - dest='shared', action='store_true', - help=_('Set shared to True (default is False).'), + help=_('Set shared flag for the firewall rule.'), default=argparse.SUPPRESS) + _add_common_args(parser) parser.add_argument( '--ip-version', type=int, choices=[4, 6], default=4, help=_('IP version for the firewall rule (default is 4).')) - parser.add_argument( - '--source-ip-address', - help=_('Source IP address or subnet.')) - parser.add_argument( - '--destination-ip-address', - help=_('Destination IP address or subnet.')) - parser.add_argument( - '--source-port', - help=_('Source port (integer in [1, 65535] or range in a:b).')) - parser.add_argument( - '--destination-port', - help=_('Destination port (integer in [1, 65535] or range in ' - 'a:b).')) - utils.add_boolean_argument( - parser, '--enabled', dest='enabled', - help=_('Whether to enable or disable this rule.')) - parser.add_argument( - '--protocol', choices=['tcp', 'udp', 'icmp', 'any'], - type=utils.convert_to_lowercase, - required=True, - help=_('Protocol for the firewall rule.')) - parser.add_argument( - '--action', - required=True, - type=utils.convert_to_lowercase, - choices=['allow', 'deny', 'reject'], - help=_('Action for the firewall rule.')) def args2body(self, parsed_args): - body = {} - neutronv20.update_dict(parsed_args, body, - ['name', 'description', 'shared', - 'source_ip_address', 'destination_ip_address', - 'source_port', 'destination_port', - 'action', 'enabled', 'tenant_id', - 'ip_version']) - protocol = parsed_args.protocol - if protocol == 'any': - protocol = None - body['protocol'] = protocol - return {self.resource: body} + return {self.resource: common_args2body(parsed_args)} class UpdateFirewallRule(neutronv20.UpdateCommand): @@ -134,22 +146,20 @@ class UpdateFirewallRule(neutronv20.UpdateCommand): resource = 'firewall_rule' def add_known_arguments(self, parser): + utils.add_boolean_argument( + parser, + '--shared', + dest='shared', + help=_('Update the shared flag for the firewall rule.'), + default=argparse.SUPPRESS) parser.add_argument( - '--protocol', choices=['tcp', 'udp', 'icmp', 'any'], - required=False, - type=utils.convert_to_lowercase, - help=_('Protocol for the firewall rule.')) - # TODO(reedip) : Need to add the option for action once - # action also comes into Update Firewall Rule + '--ip-version', + type=int, choices=[4, 6], + help=_('Update IP version for the firewall rule.')) + _add_common_args(parser, is_create=False) def args2body(self, parsed_args): - body = {} - protocol = parsed_args.protocol - if protocol: - if protocol == 'any': - protocol = None - body['protocol'] = protocol - return {self.resource: body} + return {self.resource: common_args2body(parsed_args)} class DeleteFirewallRule(neutronv20.DeleteCommand): diff --git a/neutronclient/tests/unit/fw/test_cli20_firewallrule.py b/neutronclient/tests/unit/fw/test_cli20_firewallrule.py index d84f9ed..50fabca 100644 --- a/neutronclient/tests/unit/fw/test_cli20_firewallrule.py +++ b/neutronclient/tests/unit/fw/test_cli20_firewallrule.py @@ -193,15 +193,54 @@ class CLITestV20FirewallRuleJSON(test_cli20.CLITestV20Base): ['myid', '--name', 'newname'], {'name': 'newname', }) - def test_update_firewall_rule_protocol(self): # firewall-rule-update myid --protocol any. - resource = 'firewall_rule' - cmd = firewallrule.UpdateFirewallRule(test_cli20.MyApp(sys.stdout), - None) self._test_update_resource(resource, cmd, 'myid', ['myid', '--protocol', 'any'], {'protocol': None, }) + # firewall-rule-update myid --description any + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--description', 'any'], + {'description': 'any', }) + + # firewall-rule-update myid --source_ip_address 192.192.192.192 + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--source_ip_address', + '192.192.192.192'], + {'source_ip_address': '192.192.192.192', }) + + # firewall-rule-update myid --source_port 32767 + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--source_port', '32767'], + {'source_port': '32767', }) + + # firewall-rule-update myid --destination_ip_address 0.1.0.1 + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--destination_ip_address', + '0.1.0.1'], + {'destination_ip_address': '0.1.0.1', }) + + # firewall-rule-update myid --destination_port 65432 + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--destination_port', + '65432'], + {'destination_port': '65432', }) + + # firewall-rule-update myid --enabled False + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--enabled', 'False'], + {'enabled': 'False', }) + + # firewall-rule-update myid --action reject + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--action', 'reject'], + {'action': 'reject', }) + + # firewall-rule-update myid --shared false + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--shared', 'false'], + {'shared': 'false', }) + def test_delete_firewall_rule(self): # firewall-rule-delete my-id. resource = 'firewall_rule' From da57c86a53e1842e622594e269d18bb96b7cfba6 Mon Sep 17 00:00:00 2001 From: Henry Gessau Date: Tue, 26 Jan 2016 18:56:08 -0500 Subject: [PATCH 29/57] Client bindings for Get-me-a-network Add client bindings for auto-allocated-topology. Add the 'auto-allocated-topology-show' CLI. Partially-implements: blueprint get-me-a-network Depends-On: Ia35e8a946bf0ac0bb085cde46b675d17b0bb2f51 Change-Id: I67a30ce552fd0310e051ac39dda0a763ee29ef6e --- .../neutron/v2_0/auto_allocated_topology.py | 65 +++++++++++++++++++ neutronclient/shell.py | 3 + .../unit/test_auto_allocated_topology.py | 41 ++++++++++++ neutronclient/v2_0/client.py | 8 +++ ...add-get-me-a-network-5ab2d60bf6f257b1.yaml | 9 +++ 5 files changed, 126 insertions(+) create mode 100755 neutronclient/neutron/v2_0/auto_allocated_topology.py create mode 100755 neutronclient/tests/unit/test_auto_allocated_topology.py create mode 100644 releasenotes/notes/add-get-me-a-network-5ab2d60bf6f257b1.yaml diff --git a/neutronclient/neutron/v2_0/auto_allocated_topology.py b/neutronclient/neutron/v2_0/auto_allocated_topology.py new file mode 100755 index 0000000..ad96fd9 --- /dev/null +++ b/neutronclient/neutron/v2_0/auto_allocated_topology.py @@ -0,0 +1,65 @@ +# Copyright 2016 IBM +# 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 argparse +from cliff import show +from oslo_serialization import jsonutils + +from neutronclient._i18n import _ +from neutronclient.neutron import v2_0 + + +class ShowAutoAllocatedTopology(v2_0.NeutronCommand, show.ShowOne): + """Show the auto-allocated topology of a given tenant.""" + + resource = 'auto_allocated_topology' + + def get_parser(self, prog_name): + parser = super(ShowAutoAllocatedTopology, self).get_parser(prog_name) + parser.add_argument( + '--tenant-id', metavar='tenant-id', + help=_('The owner tenant ID.')) + # Allow people to do + # neutron auto-allocated-topology-show + # (Only useful to users who can look at other tenants' topologies.) + # We use a different name for this arg because the default will + # override whatever is in the named arg otherwise. + parser.add_argument( + 'pos_tenant_id', + help=argparse.SUPPRESS, nargs='?') + return parser + + def take_action(self, parsed_args): + client = self.get_client() + tenant_id = parsed_args.tenant_id or parsed_args.pos_tenant_id + data = client.show_auto_allocated_topology(tenant_id) + if self.resource in data: + for k, v in data[self.resource].items(): + if isinstance(v, list): + value = "" + for _item in v: + if value: + value += "\n" + if isinstance(_item, dict): + value += jsonutils.dumps(_item) + else: + value += str(_item) + data[self.resource][k] = value + elif v is None: + data[self.resource][k] = '' + return zip(*sorted(data[self.resource].items())) + else: + return None diff --git a/neutronclient/shell.py b/neutronclient/shell.py index 4011133..c1f6b0a 100644 --- a/neutronclient/shell.py +++ b/neutronclient/shell.py @@ -43,6 +43,7 @@ from neutronclient.common import utils from neutronclient.neutron.v2_0 import address_scope from neutronclient.neutron.v2_0 import agent from neutronclient.neutron.v2_0 import agentscheduler +from neutronclient.neutron.v2_0 import auto_allocated_topology from neutronclient.neutron.v2_0 import availability_zone from neutronclient.neutron.v2_0 import extension from neutronclient.neutron.v2_0.flavor import flavor @@ -392,6 +393,8 @@ COMMAND_V2 = { 'flavor-profile-delete': flavor_profile.DeleteFlavorProfile, 'flavor-profile-update': flavor_profile.UpdateFlavorProfile, 'availability-zone-list': availability_zone.ListAvailabilityZone, + 'auto-allocated-topology-show': ( + auto_allocated_topology.ShowAutoAllocatedTopology), } COMMANDS = {'2.0': COMMAND_V2} diff --git a/neutronclient/tests/unit/test_auto_allocated_topology.py b/neutronclient/tests/unit/test_auto_allocated_topology.py new file mode 100755 index 0000000..b0f8cf0 --- /dev/null +++ b/neutronclient/tests/unit/test_auto_allocated_topology.py @@ -0,0 +1,41 @@ +# Copyright 2016 IBM +# 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 sys + +from neutronclient.neutron.v2_0 import auto_allocated_topology as aat +from neutronclient.tests.unit import test_cli20 + + +class TestAutoAllocatedTopologyJSON(test_cli20.CLITestV20Base): + + def test_show_auto_allocated_topology_arg(self): + resource = 'auto_allocated_topology' + cmd = aat.ShowAutoAllocatedTopology(test_cli20.MyApp(sys.stdout), None) + args = ['--tenant-id', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, args) + + def test_show_auto_allocated_topology_posarg(self): + resource = 'auto_allocated_topology' + cmd = aat.ShowAutoAllocatedTopology(test_cli20.MyApp(sys.stdout), None) + args = ['some-tenant'] + self._test_show_resource(resource, cmd, "some-tenant", args) + + def test_show_auto_allocated_topology_no_arg(self): + resource = 'auto_allocated_topology' + cmd = aat.ShowAutoAllocatedTopology(test_cli20.MyApp(sys.stdout), None) + args = [] + self._test_show_resource(resource, cmd, "None", args) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index 2d1fd6d..df0c46c 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -409,6 +409,7 @@ class Client(ClientBase): flavor_profile_bindings_path = flavor_path + service_profiles_path flavor_profile_binding_path = flavor_path + service_profile_path availability_zones_path = "/availability_zones" + auto_allocated_topology_path = "/auto-allocated-topology/%s" # API has no way to report plurals, so we have to hard code them EXTED_PLURALS = {'routers': 'router', @@ -1693,6 +1694,13 @@ class Client(ClientBase): return self.list('availability_zones', self.availability_zones_path, retrieve_all, **_params) + @APIParamsCall + def show_auto_allocated_topology(self, tenant_id, **_params): + """Fetch information about a tenant's auto-allocated topology.""" + return self.get( + self.auto_allocated_topology_path % tenant_id, + params=_params) + def __init__(self, **kwargs): """Initialize a new client for the Neutron v2.0 API.""" super(Client, self).__init__(**kwargs) diff --git a/releasenotes/notes/add-get-me-a-network-5ab2d60bf6f257b1.yaml b/releasenotes/notes/add-get-me-a-network-5ab2d60bf6f257b1.yaml new file mode 100644 index 0000000..51dd37f --- /dev/null +++ b/releasenotes/notes/add-get-me-a-network-5ab2d60bf6f257b1.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + CLI support for the "get-me-a-network" feature, which simplifies + the process for launching an instance with basic network + connectivity. + + * The ``auto-allocated-topology-show`` command provides the + network of the automatically allocated topology for a tenant. From 92b5fe9eb1aa0dca75129fd0f0f7da25f312623b Mon Sep 17 00:00:00 2001 From: Reedip Banerjee Date: Thu, 10 Dec 2015 09:01:27 +0530 Subject: [PATCH 30/57] Provide argument filtering in neutron *-list This implements a common framework for attribute filtering arguments and arguments for net-list as an initial support. Co-Authored-By: Reedip Banerjee Co-Authored-By: Akihiro Motoki Change-Id: Ie1b896d6f293b2881a7067ed9232a5957a5180cb Closes-Bug: #1320798 Partial-Bug: #1488912 --- neutronclient/neutron/v2_0/__init__.py | 66 ++++++++++++++++ neutronclient/neutron/v2_0/network.py | 17 +++++ neutronclient/tests/unit/test_cli20.py | 75 +++++++++++++++++++ .../tests/unit/test_cli20_network.py | 12 ++- 4 files changed, 168 insertions(+), 2 deletions(-) diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index 34c5592..f37fcb1 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -18,6 +18,7 @@ from __future__ import print_function import abc import argparse +import functools import logging import re @@ -603,6 +604,37 @@ class ListCommand(NeutronCommand, lister.Lister): unknown_parts_flag = True pagination_support = False sorting_support = False + resource_plural = None + + # A list to define arguments for filtering by attribute value + # CLI arguments are shown in the order of this list. + # Each element must be either of a string of an attribute name + # or a dict of a full attribute definitions whose format is: + # {'name': attribute name, (mandatory) + # 'help': help message for CLI (mandatory) + # 'boolean': boolean parameter or not. (Default: False) (optional) + # 'argparse_kwargs': a dict of parameters passed to + # argparse add_argument() + # (Default: {}) (optional) + # } + # For more details, see ListNetworks.filter_attrs. + filter_attrs = [] + + default_attr_defs = { + 'name': { + 'help': _("Filter %s according to their name."), + 'boolean': False, + }, + 'tenant_id': { + 'help': _('Filter %s belonging to the given tenant.'), + 'boolean': False, + }, + 'admin_state_up': { + 'help': _('Filter and list the %s whose adminstrative ' + 'state is active'), + 'boolean': True, + }, + } def get_parser(self, prog_name): parser = super(ListCommand, self).get_parser(prog_name) @@ -612,8 +644,36 @@ class ListCommand(NeutronCommand, lister.Lister): if self.sorting_support: add_sorting_argument(parser) self.add_known_arguments(parser) + self.add_filtering_arguments(parser) return parser + def add_filtering_arguments(self, parser): + if not self.filter_attrs: + return + + group_parser = parser.add_argument_group('filtering arguments') + collection = self.resource_plural or '%ss' % self.resource + for attr in self.filter_attrs: + if isinstance(attr, str): + # Use detail defined in default_attr_defs + attr_name = attr + attr_defs = self.default_attr_defs[attr] + else: + attr_name = attr['name'] + attr_defs = attr + option_name = '--%s' % attr_name.replace('_', '-') + params = attr_defs.get('argparse_kwargs', {}) + try: + help_msg = attr_defs['help'] % collection + except TypeError: + help_msg = attr_defs['help'] + if attr_defs.get('boolean', False): + add_arg_func = functools.partial(utils.add_boolean_argument, + group_parser) + else: + add_arg_func = group_parser.add_argument + add_arg_func(option_name, help=help_msg, **params) + def args2search_opts(self, parsed_args): search_opts = {} fields = parsed_args.fields @@ -621,6 +681,12 @@ class ListCommand(NeutronCommand, lister.Lister): search_opts.update({'fields': fields}) if parsed_args.show_details: search_opts.update({'verbose': 'True'}) + filter_attrs = [field if isinstance(field, str) else field['name'] + for field in self.filter_attrs] + for attr in filter_attrs: + val = getattr(parsed_args, attr, None) + if val: + search_opts[attr] = val return search_opts def call_server(self, neutron_client, search_opts, parsed_args): diff --git a/neutronclient/neutron/v2_0/network.py b/neutronclient/neutron/v2_0/network.py index 8d34270..e5ac3bd 100644 --- a/neutronclient/neutron/v2_0/network.py +++ b/neutronclient/neutron/v2_0/network.py @@ -44,6 +44,23 @@ class ListNetwork(neutronV20.ListCommand): pagination_support = True sorting_support = True + filter_attrs = [ + 'tenant_id', + 'name', + 'admin_state_up', + {'name': 'status', + 'help': _("Filter %s according to their operation status." + "(For example: ACTIVE, ERROR etc)"), + 'boolean': False, + 'argparse_kwargs': {'type': utils.convert_to_uppercase}}, + {'name': 'shared', + 'help': _('Filter and list the networks which are shared.'), + 'boolean': True}, + {'name': 'router:external', + 'help': _('Filter and list the networks which are external.'), + 'boolean': True}, + ] + def extend_list(self, data, parsed_args): """Add subnet information to a network list.""" neutron_client = self.get_client() diff --git a/neutronclient/tests/unit/test_cli20.py b/neutronclient/tests/unit/test_cli20.py index febc586..8aa0aab 100644 --- a/neutronclient/tests/unit/test_cli20.py +++ b/neutronclient/tests/unit/test_cli20.py @@ -557,6 +557,81 @@ class CLITestV20Base(base.BaseTestCase): self.assertIn(myid, _str) +class TestListCommand(neutronV2_0.ListCommand): + resource = 'test_resource' + filter_attrs = [ + 'name', + 'admin_state_up', + {'name': 'foo', 'help': 'non-boolean attribute foo'}, + {'name': 'bar', 'help': 'boolean attribute bar', + 'boolean': True}, + {'name': 'baz', 'help': 'integer attribute baz', + 'argparse_kwargs': {'choices': ['baz1', 'baz2']}}, + ] + + +class ListCommandTestCase(CLITestV20Base): + + def setUp(self): + super(ListCommandTestCase, self).setUp() + self.client.extend_list('test_resources', '/test_resources', None) + setattr(self.client, 'test_resources_path', '/test_resources') + + def _test_list_resources_filter_params(self, base_args='', query=''): + resources = 'test_resources' + cmd = TestListCommand(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + base_args=base_args.split(), + query=query) + + def _test_list_resources_with_arg_error(self, base_args=''): + self.addCleanup(self.mox.UnsetStubs) + resources = 'test_resources' + cmd = TestListCommand(MyApp(sys.stdout), None) + # argparse parse error leads to SystemExit + self.assertRaises(SystemExit, + self._test_list_resources, + resources, cmd, + base_args=base_args.split()) + + def test_list_resources_without_filter(self): + self._test_list_resources_filter_params() + + def test_list_resources_use_default_filter(self): + self._test_list_resources_filter_params( + base_args='--name val1 --admin-state-up False', + query='name=val1&admin_state_up=False') + + def test_list_resources_use_custom_filter(self): + self._test_list_resources_filter_params( + base_args='--foo FOO --bar True', + query='foo=FOO&bar=True') + + def test_list_resources_boolean_check_default_filter(self): + self._test_list_resources_filter_params( + base_args='--admin-state-up True', query='admin_state_up=True') + self._test_list_resources_filter_params( + base_args='--admin-state-up False', query='admin_state_up=False') + self._test_list_resources_with_arg_error( + base_args='--admin-state-up non-true-false') + + def test_list_resources_boolean_check_custom_filter(self): + self._test_list_resources_filter_params( + base_args='--bar True', query='bar=True') + self._test_list_resources_filter_params( + base_args='--bar False', query='bar=False') + self._test_list_resources_with_arg_error( + base_args='--bar non-true-false') + + def test_list_resources_argparse_kwargs(self): + self._test_list_resources_filter_params( + base_args='--baz baz1', query='baz=baz1') + self._test_list_resources_filter_params( + base_args='--baz baz2', query='baz=baz2') + self._test_list_resources_with_arg_error( + base_args='--bar non-choice') + + class ClientV2TestJson(CLITestV20Base): def test_do_request_unicode(self): self.mox.StubOutWithMock(self.client.httpclient, "request") diff --git a/neutronclient/tests/unit/test_cli20_network.py b/neutronclient/tests/unit/test_cli20_network.py index 8d264c4..0465b88 100644 --- a/neutronclient/tests/unit/test_cli20_network.py +++ b/neutronclient/tests/unit/test_cli20_network.py @@ -196,13 +196,15 @@ class CLITestV20NetworkJSON(test_cli20.CLITestV20Base): def _test_list_networks(self, cmd, detail=False, tags=(), fields_1=(), fields_2=(), page_size=None, - sort_key=(), sort_dir=()): + sort_key=(), sort_dir=(), base_args=None, + query=''): resources = "networks" self.mox.StubOutWithMock(network.ListNetwork, "extend_list") network.ListNetwork.extend_list(mox.IsA(list), mox.IgnoreArg()) self._test_list_resources(resources, cmd, detail, tags, fields_1, fields_2, page_size=page_size, - sort_key=sort_key, sort_dir=sort_dir) + sort_key=sort_key, sort_dir=sort_dir, + base_args=base_args, query=query) def test_list_nets_pagination(self): cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) @@ -611,3 +613,9 @@ class CLITestV20NetworkJSON(test_cli20.CLITestV20Base): 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(response) self._test_extend_list(mox_calls) + + def test_list_shared_networks(self): + # list nets : --shared False + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) + self._test_list_networks(cmd, base_args='--shared False'.split(), + query='shared=False') From 6810d96ea103b46b42de9073dd6c913b75626fb3 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 11 Feb 2016 07:44:36 +0000 Subject: [PATCH 31/57] Updated from global requirements Change-Id: I4fe34e07dcb5ffd16f849e95483d5b4aaaf5102c --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8704380..c5280b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr>=1.6 # Apache-2.0 -cliff>=1.15.0 # Apache-2.0 +cliff!=1.16.0,>=1.15.0 # Apache-2.0 debtcollector>=1.2.0 # Apache-2.0 iso8601>=0.1.9 # MIT netaddr!=0.7.16,>=0.7.12 # BSD diff --git a/test-requirements.txt b/test-requirements.txt index 35c9b6b..992d131 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -16,4 +16,4 @@ requests-mock>=0.7.0 # Apache-2.0 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT -tempest-lib>=0.13.0 # Apache-2.0 +tempest-lib>=0.14.0 # Apache-2.0 From 53195f701883b8e0309d3926cb5e1d2c26a4b3f5 Mon Sep 17 00:00:00 2001 From: cshahani Date: Wed, 10 Feb 2016 22:53:38 -0800 Subject: [PATCH 32/57] Fix typos in the docstrings Fixed some typos in the docstrings for methods and corrected the docstring for a method. Change-Id: I33860de72988e2160ec65d96c9e118f7c9861788 --- neutronclient/v2_0/client.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index df0c46c..cc323c9 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -451,27 +451,27 @@ class Client(ClientBase): @APIParamsCall def list_ext(self, collection, path, retrieve_all, **_params): - """Client extension hook for lists.""" + """Client extension hook for list.""" return self.list(collection, path, retrieve_all, **_params) @APIParamsCall def show_ext(self, path, id, **_params): - """Client extension hook for shows.""" + """Client extension hook for show.""" return self.get(path % id, params=_params) @APIParamsCall def create_ext(self, path, body=None): - """Client extension hook for creates.""" + """Client extension hook for create.""" return self.post(path, body=body) @APIParamsCall def update_ext(self, path, id, body=None): - """Client extension hook for updates.""" + """Client extension hook for update.""" return self.put(path % id, body=body) @APIParamsCall def delete_ext(self, path, id): - """Client extension hook for deletes.""" + """Client extension hook for delete.""" return self.delete(path % id) @APIParamsCall @@ -501,24 +501,24 @@ class Client(ClientBase): @APIParamsCall def list_extensions(self, **_params): - """Fetch a list of all exts on server side.""" + """Fetch a list of all extensions on server side.""" return self.get(self.extensions_path, params=_params) @APIParamsCall def show_extension(self, ext_alias, **_params): - """Fetch a list of all exts on server side.""" + """Fetches information of a certain extension.""" return self.get(self.extension_path % ext_alias, params=_params) @APIParamsCall def list_ports(self, retrieve_all=True, **_params): - """Fetches a list of all networks for a tenant.""" + """Fetches a list of all ports for a tenant.""" # Pass filters in "params" argument to do_request return self.list('ports', self.ports_path, retrieve_all, **_params) @APIParamsCall def show_port(self, port, **_params): - """Fetches information of a certain network.""" + """Fetches information of a certain port.""" return self.get(self.port_path % (port), params=_params) @APIParamsCall @@ -565,7 +565,7 @@ class Client(ClientBase): @APIParamsCall def list_subnets(self, retrieve_all=True, **_params): - """Fetches a list of all networks for a tenant.""" + """Fetches a list of all subnets for a tenant.""" return self.list('subnets', self.subnets_path, retrieve_all, **_params) @@ -1419,7 +1419,7 @@ class Client(ClientBase): @APIParamsCall def list_firewalls(self, retrieve_all=True, **_params): - """Fetches a list of all firewals for a tenant.""" + """Fetches a list of all firewalls for a tenant.""" # Pass filters in "params" argument to do_request return self.list('firewalls', self.firewalls_path, retrieve_all, From 9c3ad54ba45569f201bef793bba9f7cf805c1124 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Fri, 19 Feb 2016 07:41:04 +0900 Subject: [PATCH 33/57] Fix typo in the help of net-list TrivialFix Change-Id: If20a2dcbacd5230b9f569478ab97c9114c15301b --- neutronclient/neutron/v2_0/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index f37fcb1..82848e9 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -630,7 +630,7 @@ class ListCommand(NeutronCommand, lister.Lister): 'boolean': False, }, 'admin_state_up': { - 'help': _('Filter and list the %s whose adminstrative ' + 'help': _('Filter and list the %s whose administrative ' 'state is active'), 'boolean': True, }, From 3e3baba4f232de7fd04e05bb6e9afdc723668594 Mon Sep 17 00:00:00 2001 From: reedip Date: Wed, 27 Jan 2016 17:00:25 +0900 Subject: [PATCH 34/57] Fix the exception when ID/Name not found Currently NeutronClient raises a broad exception (NeutronClientException) from [1]-[2], which is not appropriate. The following patch proposes using the NotFound exception which does the same work. [1]:http://git.openstack.org/cgit/openstack/python-neutronclient/tree/neutronclient/neutron/v2_0/__init__.py#n72 [2]:http://git.openstack.org/cgit/openstack/python-neutronclient/tree/neutronclient/neutron/v2_0/__init__.py#n109 Change-Id: Ib23d1e53947b5dffcff8db0dde77cae0a0b31243 --- neutronclient/neutron/v2_0/__init__.py | 12 +++++------- neutronclient/tests/unit/test_name_or_id.py | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index 34c5592..702a50c 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -68,9 +68,8 @@ def find_resource_by_id(client, resource, resource_id, cmd_resource=None, not_found_message = (_("Unable to find %(resource)s with id " "'%(id)s'") % {'resource': resource, 'id': resource_id}) - # 404 is used to simulate server side behavior - raise exceptions.NeutronClientException( - message=not_found_message, status_code=404) + # 404 is raised by exceptions.NotFound to simulate serverside behavior + raise exceptions.NotFound(message=not_found_message) def find_resourceid_by_id(client, resource, resource_id, cmd_resource=None, @@ -105,9 +104,8 @@ def _find_resource_by_name(client, resource, name, project_id=None, not_found_message = (_("Unable to find %(resource)s with name " "'%(name)s'") % {'resource': resource, 'name': name}) - # 404 is used to simulate server side behavior - raise exceptions.NeutronClientException( - message=not_found_message, status_code=404) + # 404 is raised by exceptions.NotFound to simulate serverside behavior + raise exceptions.NotFound(message=not_found_message) else: return info[0] @@ -118,7 +116,7 @@ def find_resource_by_name_or_id(client, resource, name_or_id, try: return find_resource_by_id(client, resource, name_or_id, cmd_resource, parent_id, fields) - except exceptions.NeutronClientException: + except exceptions.NotFound: return _find_resource_by_name(client, resource, name_or_id, project_id, cmd_resource, parent_id, fields) diff --git a/neutronclient/tests/unit/test_name_or_id.py b/neutronclient/tests/unit/test_name_or_id.py index 412c455..47b6b9b 100644 --- a/neutronclient/tests/unit/test_name_or_id.py +++ b/neutronclient/tests/unit/test_name_or_id.py @@ -144,7 +144,7 @@ class CLITestNameorID(testtools.TestCase): try: neutronV20.find_resourceid_by_name_or_id( self.client, 'network', name) - except exceptions.NeutronClientException as ex: + except exceptions.NotFound as ex: self.assertIn('Unable to find', ex.message) self.assertEqual(404, ex.status_code) @@ -193,6 +193,6 @@ class CLITestNameorID(testtools.TestCase): try: neutronV20.find_resourceid_by_name_or_id( self.client, 'security_group', name, project) - except exceptions.NeutronClientException as ex: + except exceptions.NotFound as ex: self.assertIn('Unable to find', ex.message) self.assertEqual(404, ex.status_code) From 44da935264ab339d52bcbece1746c16301f28679 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 20 Feb 2016 22:00:30 +0000 Subject: [PATCH 35/57] Updated from global requirements Change-Id: Ie017f8c7acd17384b98743b79888c4e821d7497f --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c5280b9..c1142eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ iso8601>=0.1.9 # MIT netaddr!=0.7.16,>=0.7.12 # BSD oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 -oslo.utils>=3.4.0 # Apache-2.0 +oslo.utils>=3.5.0 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 requests!=2.9.0,>=2.8.1 # Apache-2.0 From ed17ae60bd5fbcfffb94e91f270191f0b4d92970 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Sun, 6 Dec 2015 16:55:24 +0900 Subject: [PATCH 36/57] Improve str2dict key validation to avoid wrong keys This commit adds valid_keys and required_keys to str2dict and define a new function which can be used as argparse type validator. By this function, we can declare what fields are valid and what fields are required for dictionary option in option definition. Change-Id: Ib7b233e082c15d0bd6e16a754b8acad52e413986 Closes-Bug: #1102897 --- neutronclient/common/utils.py | 36 +++++++++++++++++-- neutronclient/neutron/v2_0/lb/v2/pool.py | 5 ++- neutronclient/neutron/v2_0/port.py | 19 ++++++---- neutronclient/neutron/v2_0/router.py | 12 ++++--- neutronclient/neutron/v2_0/subnet.py | 9 +++-- neutronclient/neutron/v2_0/vpn/ikepolicy.py | 4 +-- .../neutron/v2_0/vpn/ipsec_site_connection.py | 3 +- neutronclient/neutron/v2_0/vpn/ipsecpolicy.py | 4 +-- neutronclient/tests/unit/test_cli20_port.py | 9 ++--- neutronclient/tests/unit/test_utils.py | 30 ++++++++++++++++ .../tests/unit/vpn/test_cli20_ikepolicy.py | 18 +++++----- .../vpn/test_cli20_ipsec_site_connection.py | 8 +++-- .../tests/unit/vpn/test_cli20_ipsecpolicy.py | 24 ++++++++----- 13 files changed, 132 insertions(+), 49 deletions(-) diff --git a/neutronclient/common/utils.py b/neutronclient/common/utils.py index 4f19dac..47610e6 100644 --- a/neutronclient/common/utils.py +++ b/neutronclient/common/utils.py @@ -18,6 +18,7 @@ """Utilities and helper functions.""" import argparse +import functools import logging import netaddr import os @@ -108,10 +109,21 @@ def str2bool(strbool): return strbool.lower() == 'true' -def str2dict(strdict): +def str2dict(strdict, required_keys=None, optional_keys=None): """Convert key1=value1,key2=value2,... string into dictionary. - :param strdict: key1=value1,key2=value2 + :param strdict: string in the form of key1=value1,key2=value2 + :param required_keys: list of required keys. All keys in this list must be + specified. Otherwise ArgumentTypeError will be raised. + If this parameter is unspecified, no required key check + will be done. + :param optional_keys: list of optional keys. + This parameter is used for valid key check. + When at least one of required_keys and optional_keys, + a key must be a member of either of required_keys or + optional_keys. Otherwise, ArgumentTypeError will be + raised. When both required_keys and optional_keys are + unspecified, no valid key check will be done. """ result = {} if strdict: @@ -121,9 +133,29 @@ def str2dict(strdict): msg = _("invalid key-value '%s', expected format: key=value") raise argparse.ArgumentTypeError(msg % kv) result[key] = value + valid_keys = set(required_keys or []) | set(optional_keys or []) + if valid_keys: + invalid_keys = [k for k in result if k not in valid_keys] + if invalid_keys: + msg = _("Invalid key(s) '%(invalid_keys)s' specified. " + "Valid key(s): '%(valid_keys)s'.") + raise argparse.ArgumentTypeError( + msg % {'invalid_keys': ', '.join(sorted(invalid_keys)), + 'valid_keys': ', '.join(sorted(valid_keys))}) + if required_keys: + not_found_keys = [k for k in required_keys if k not in result] + if not_found_keys: + msg = _("Required key(s) '%s' not specified.") + raise argparse.ArgumentTypeError(msg % ', '.join(not_found_keys)) return result +def str2dict_type(optional_keys=None, required_keys=None): + return functools.partial(str2dict, + optional_keys=optional_keys, + required_keys=required_keys) + + def http_log_req(_logger, args, kwargs): if not _logger.isEnabledFor(logging.DEBUG): return diff --git a/neutronclient/neutron/v2_0/lb/v2/pool.py b/neutronclient/neutron/v2_0/lb/v2/pool.py index 3c1ea99..2745091 100644 --- a/neutronclient/neutron/v2_0/lb/v2/pool.py +++ b/neutronclient/neutron/v2_0/lb/v2/pool.py @@ -64,6 +64,8 @@ class CreatePool(neutronV20.CreateCommand): parser.add_argument( '--session-persistence', metavar='type=TYPE[,cookie_name=COOKIE_NAME]', + type=utils.str2dict_type(required_keys=['type'], + optional_keys=['cookie_name']), help=_('The type of session persistence to use and associated ' 'cookie name')) parser.add_argument( @@ -86,9 +88,6 @@ class CreatePool(neutronV20.CreateCommand): help=_('Protocol for balancing.')) def args2body(self, parsed_args): - if parsed_args.session_persistence: - parsed_args.session_persistence = utils.str2dict( - parsed_args.session_persistence) _listener_id = neutronV20.find_resourceid_by_name_or_id( self.get_client(), 'listener', parsed_args.listener) body = {'admin_state_up': parsed_args.admin_state, diff --git a/neutronclient/neutron/v2_0/port.py b/neutronclient/neutron/v2_0/port.py index 0e27cd2..9d71e1a 100644 --- a/neutronclient/neutron/v2_0/port.py +++ b/neutronclient/neutron/v2_0/port.py @@ -46,6 +46,7 @@ def _add_updatable_args(parser): parser.add_argument( '--fixed-ip', metavar='subnet_id=SUBNET,ip_address=IP_ADDR', action='append', + type=utils.str2dict_type(optional_keys=['subnet_id', 'ip_address']), help=_('Desired IP and/or subnet for this port: ' 'subnet_id=,ip_address=. ' 'You can repeat this option.')) @@ -73,13 +74,12 @@ def _updatable_args2body(parsed_args, body, client): ips = [] if parsed_args.fixed_ip: for ip_spec in parsed_args.fixed_ip: - ip_dict = utils.str2dict(ip_spec) - if 'subnet_id' in ip_dict: - subnet_name_id = ip_dict['subnet_id'] + if 'subnet_id' in ip_spec: + subnet_name_id = ip_spec['subnet_id'] _subnet_id = neutronV20.find_resourceid_by_name_or_id( client, 'subnet', subnet_name_id) - ip_dict['subnet_id'] = _subnet_id - ips.append(ip_dict) + ip_spec['subnet_id'] = _subnet_id + ips.append(ip_spec) if ips: body['fixed_ips'] = ips @@ -158,6 +158,9 @@ class UpdateExtraDhcpOptMixin(object): default=[], action='append', dest='extra_dhcp_opts', + type=utils.str2dict_type( + required_keys=['opt_name'], + optional_keys=['opt_value', 'ip_version']), help=_('Extra dhcp options to be assigned to this port: ' 'opt_name=,opt_value=,' 'ip_version={4,6}. You can repeat this option.')) @@ -174,7 +177,7 @@ class UpdateExtraDhcpOptMixin(object): "ip_version={4,6}. " "You can repeat this option.") for opt in parsed_args.extra_dhcp_opts: - opt_ele.update(utils.str2dict(opt)) + opt_ele.update(opt) if ('opt_name' in opt_ele and ('opt_value' in opt_ele or 'ip_version' in opt_ele)): if opt_ele.get('opt_value') == 'null': @@ -199,7 +202,9 @@ class UpdatePortAllowedAddressPair(object): default=[], action='append', dest='allowed_address_pairs', - type=utils.str2dict, + type=utils.str2dict_type( + required_keys=['ip_address'], + optional_keys=['mac_address']), help=_('Allowed address pair associated with the port.' 'You can repeat this option.')) group_aap.add_argument( diff --git a/neutronclient/neutron/v2_0/router.py b/neutronclient/neutron/v2_0/router.py index 88da856..c372f98 100644 --- a/neutronclient/neutron/v2_0/router.py +++ b/neutronclient/neutron/v2_0/router.py @@ -114,7 +114,8 @@ class UpdateRouter(neutronV20.UpdateCommand): routes_group = parser.add_mutually_exclusive_group() routes_group.add_argument( '--route', metavar='destination=CIDR,nexthop=IP_ADDR', - action='append', dest='routes', type=utils.str2dict, + action='append', dest='routes', + type=utils.str2dict_type(required_keys=['destination', 'nexthop']), help=_('Route to associate with the router.' ' You can repeat this option.')) routes_group.add_argument( @@ -226,6 +227,8 @@ class SetGatewayRouter(neutronV20.NeutronCommand): parser.add_argument( '--fixed-ip', metavar='subnet_id=SUBNET,ip_address=IP_ADDR', action='append', + type=utils.str2dict_type(optional_keys=['subnet_id', + 'ip_address']), help=_('Desired IP and/or subnet on external network: ' 'subnet_id=,ip_address=. ' 'You can specify both of subnet_id and ip_address or ' @@ -246,13 +249,12 @@ class SetGatewayRouter(neutronV20.NeutronCommand): if parsed_args.fixed_ip: ips = [] for ip_spec in parsed_args.fixed_ip: - ip_dict = utils.str2dict(ip_spec) - subnet_name_id = ip_dict.get('subnet_id') + subnet_name_id = ip_spec.get('subnet_id') if subnet_name_id: subnet_id = neutronV20.find_resourceid_by_name_or_id( neutron_client, 'subnet', subnet_name_id) - ip_dict['subnet_id'] = subnet_id - ips.append(ip_dict) + ip_spec['subnet_id'] = subnet_id + ips.append(ip_spec) router_dict['external_fixed_ips'] = ips neutron_client.add_gateway_router(_router_id, router_dict) print(_('Set gateway for router %s') % parsed_args.router, diff --git a/neutronclient/neutron/v2_0/subnet.py b/neutronclient/neutron/v2_0/subnet.py index d8b5946..3abe313 100644 --- a/neutronclient/neutron/v2_0/subnet.py +++ b/neutronclient/neutron/v2_0/subnet.py @@ -62,16 +62,19 @@ def add_updatable_arguments(parser): help=_('No distribution of gateway.')) parser.add_argument( '--allocation-pool', metavar='start=IP_ADDR,end=IP_ADDR', - action='append', dest='allocation_pools', type=utils.str2dict, + action='append', dest='allocation_pools', + type=utils.str2dict_type(required_keys=['start', 'end']), help=_('Allocation pool IP addresses for this subnet ' '(This option can be repeated).')) parser.add_argument( '--allocation_pool', - action='append', dest='allocation_pools', type=utils.str2dict, + action='append', dest='allocation_pools', + type=utils.str2dict_type(required_keys=['start', 'end']), help=argparse.SUPPRESS) parser.add_argument( '--host-route', metavar='destination=CIDR,nexthop=IP_ADDR', - action='append', dest='host_routes', type=utils.str2dict, + action='append', dest='host_routes', + type=utils.str2dict_type(required_keys=['destination', 'nexthop']), help=_('Additional route (This option can be repeated).')) parser.add_argument( '--dns-nameserver', metavar='DNS_NAMESERVER', diff --git a/neutronclient/neutron/v2_0/vpn/ikepolicy.py b/neutronclient/neutron/v2_0/vpn/ikepolicy.py index 5090831..fe41655 100644 --- a/neutronclient/neutron/v2_0/vpn/ikepolicy.py +++ b/neutronclient/neutron/v2_0/vpn/ikepolicy.py @@ -71,7 +71,7 @@ class CreateIKEPolicy(neutronv20.CreateCommand): parser.add_argument( '--lifetime', metavar="units=UNITS,value=VALUE", - type=utils.str2dict, + type=utils.str2dict_type(optional_keys=['units', 'value']), help=vpn_utils.lifetime_help("IKE")) parser.add_argument( 'name', metavar='NAME', @@ -100,7 +100,7 @@ class UpdateIKEPolicy(neutronv20.UpdateCommand): parser.add_argument( '--lifetime', metavar="units=UNITS,value=VALUE", - type=utils.str2dict, + type=utils.str2dict_type(optional_keys=['units', 'value']), help=vpn_utils.lifetime_help("IKE")) def args2body(self, parsed_args): diff --git a/neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py b/neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py index bace4d2..af596c3 100644 --- a/neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py +++ b/neutronclient/neutron/v2_0/vpn/ipsec_site_connection.py @@ -55,7 +55,8 @@ class IPsecSiteConnectionMixin(object): parser.add_argument( '--dpd', metavar="action=ACTION,interval=INTERVAL,timeout=TIMEOUT", - type=utils.str2dict, + type=utils.str2dict_type( + optional_keys=['action', 'interval', 'timeout']), help=vpn_utils.dpd_help("IPsec connection.")) parser.add_argument( '--local-ep-group', diff --git a/neutronclient/neutron/v2_0/vpn/ipsecpolicy.py b/neutronclient/neutron/v2_0/vpn/ipsecpolicy.py index 76f0914..8f62964 100644 --- a/neutronclient/neutron/v2_0/vpn/ipsecpolicy.py +++ b/neutronclient/neutron/v2_0/vpn/ipsecpolicy.py @@ -70,7 +70,7 @@ class CreateIPsecPolicy(neutronv20.CreateCommand): parser.add_argument( '--lifetime', metavar="units=UNITS,value=VALUE", - type=utils.str2dict, + type=utils.str2dict_type(optional_keys=['units', 'value']), help=vpn_utils.lifetime_help("IPsec")) parser.add_argument( 'name', metavar='NAME', @@ -99,7 +99,7 @@ class UpdateIPsecPolicy(neutronv20.UpdateCommand): parser.add_argument( '--lifetime', metavar="units=UNITS,value=VALUE", - type=utils.str2dict, + type=utils.str2dict_type(optional_keys=['units', 'value']), help=vpn_utils.lifetime_help("IPsec")) def args2body(self, parsed_args): diff --git a/neutronclient/tests/unit/test_cli20_port.py b/neutronclient/tests/unit/test_cli20_port.py index 4b6d617..6ed3134 100644 --- a/neutronclient/tests/unit/test_cli20_port.py +++ b/neutronclient/tests/unit/test_cli20_port.py @@ -589,13 +589,14 @@ class CLITestV20PortJSON(test_cli20.CLITestV20Base): resource = 'port' cmd = port.UpdatePort(test_cli20.MyApp(sys.stdout), None) myid = 'myid' - net_id = 'net_id' + subnet_id = 'subnet_id' ip_addr = '123.123.123.123' args = [myid, - '--fixed-ip', "network_id=%(net_id)s,ip_address=%(ip_addr)s" % - {'net_id': net_id, + '--fixed-ip', + "subnet_id=%(subnet_id)s,ip_address=%(ip_addr)s" % + {'subnet_id': subnet_id, 'ip_addr': ip_addr}] - updated_fields = {"fixed_ips": [{'network_id': net_id, + updated_fields = {"fixed_ips": [{'subnet_id': subnet_id, 'ip_address': ip_addr}]} self._test_update_resource(resource, cmd, myid, args, updated_fields) diff --git a/neutronclient/tests/unit/test_utils.py b/neutronclient/tests/unit/test_utils.py index 27ebc0f..4415241 100644 --- a/neutronclient/tests/unit/test_utils.py +++ b/neutronclient/tests/unit/test_utils.py @@ -49,6 +49,36 @@ class TestUtils(testtools.TestCase): self.assertRaises(argparse.ArgumentTypeError, utils.str2dict, input_str) + def test_str2dict_optional_keys(self): + self.assertDictEqual({'key1': 'value1'}, + utils.str2dict('key1=value1', + optional_keys=['key1', 'key2'])) + self.assertDictEqual({'key1': 'value1', 'key2': 'value2'}, + utils.str2dict('key1=value1,key2=value2', + optional_keys=['key1', 'key2'])) + e = self.assertRaises(argparse.ArgumentTypeError, + utils.str2dict, + 'key1=value1,key2=value2,key3=value3', + optional_keys=['key1', 'key2']) + self.assertEqual("Invalid key(s) 'key3' specified. " + "Valid key(s): 'key1, key2'.", + str(e)) + + def test_str2dict_required_keys(self): + self.assertDictEqual( + {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}, + utils.str2dict('key1=value1,key2=value2,key3=value3', + required_keys=['key1', 'key2'], + optional_keys=['key3'])) + self.assertDictEqual( + {'key1': 'value1', 'key2': 'value2'}, + utils.str2dict('key1=value1,key2=value2', + required_keys=['key1', 'key2'])) + e = self.assertRaises(argparse.ArgumentTypeError, + utils.str2dict, 'key1=value1', + required_keys=['key1', 'key2']) + self.assertEqual("Required key(s) 'key2' not specified.", str(e)) + def test_get_dict_item_properties(self): item = {'name': 'test_name', 'id': 'test_id'} fields = ('name', 'id') diff --git a/neutronclient/tests/unit/vpn/test_cli20_ikepolicy.py b/neutronclient/tests/unit/vpn/test_cli20_ikepolicy.py index d7d1bbf..c6ac8fb 100644 --- a/neutronclient/tests/unit/vpn/test_cli20_ikepolicy.py +++ b/neutronclient/tests/unit/vpn/test_cli20_ikepolicy.py @@ -16,6 +16,7 @@ import sys +from neutronclient.common import exceptions from neutronclient.neutron.v2_0.vpn import ikepolicy from neutronclient.tests.unit import test_cli20 @@ -101,7 +102,7 @@ class CLITestV20VpnIkePolicyJSON(test_cli20.CLITestV20Base): self._test_create_resource(resource, cmd, name, my_id, args, position_names, position_values) - def _test_lifetime_values(self, lifetime): + def _test_lifetime_values(self, lifetime, expected_exc=None): resource = 'ikepolicy' cmd = ikepolicy.CreateIKEPolicy(test_cli20.MyApp(sys.stdout), None) name = 'ikepolicy1' @@ -134,16 +135,17 @@ class CLITestV20VpnIkePolicyJSON(test_cli20.CLITestV20Base): auth_algorithm, encryption_algorithm, phase1_negotiation_mode, ike_version, pfs, tenant_id] - try: - self._test_create_resource(resource, cmd, name, my_id, args, - position_names, position_values) - except Exception: - return - self.fail("IKEPolicy Lifetime Error") + if not expected_exc: + expected_exc = exceptions.CommandError + self.assertRaises( + expected_exc, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) def test_create_ikepolicy_with_invalid_lifetime_keys(self): lifetime = 'uts=seconds,val=20000' - self._test_lifetime_values(lifetime) + self._test_lifetime_values(lifetime, expected_exc=SystemExit) def test_create_ikepolicy_with_invalid_lifetime_value(self): lifetime = 'units=seconds,value=-1' diff --git a/neutronclient/tests/unit/vpn/test_cli20_ipsec_site_connection.py b/neutronclient/tests/unit/vpn/test_cli20_ipsec_site_connection.py index 85a28b6..8ead88f 100644 --- a/neutronclient/tests/unit/vpn/test_cli20_ipsec_site_connection.py +++ b/neutronclient/tests/unit/vpn/test_cli20_ipsec_site_connection.py @@ -180,7 +180,7 @@ class CLITestV20IPsecSiteConnectionJSON(test_cli20.CLITestV20Base): self._test_create_resource(resource, cmd, None, my_id, args, position_names, position_values) - def _test_create_failure(self, additional_args=None): + def _test_create_failure(self, additional_args=None, expected_exc=None): # Helper to test failure of IPSec site-to-site creation failure. resource = 'ipsec_site_connection' cmd = ipsec_site_connection.CreateIPsecSiteConnection( @@ -215,7 +215,9 @@ class CLITestV20IPsecSiteConnectionJSON(test_cli20.CLITestV20Base): position_values = [tenant_id, admin_state, peer_address, peer_id, psk, mtu, initiator, None, None, vpnservice_id, ikepolicy_id, ipsecpolicy_id] - self.assertRaises(exceptions.CommandError, + if not expected_exc: + expected_exc = exceptions.CommandError + self.assertRaises(expected_exc, self._test_create_resource, resource, cmd, None, my_id, args, position_names, position_values) @@ -227,7 +229,7 @@ class CLITestV20IPsecSiteConnectionJSON(test_cli20.CLITestV20Base): def test_fail_create_with_invalid_dpd_keys(self): bad_dpd_key = ['--dpd', 'act=restart,interval=30,time=120'] - self._test_create_failure(bad_dpd_key) + self._test_create_failure(bad_dpd_key, SystemExit) def test_fail_create_with_invalid_dpd_values(self): bad_dpd_values = ['--dpd', 'action=hold,interval=30,timeout=-1'] diff --git a/neutronclient/tests/unit/vpn/test_cli20_ipsecpolicy.py b/neutronclient/tests/unit/vpn/test_cli20_ipsecpolicy.py index 5df90ba..c6b23e1 100644 --- a/neutronclient/tests/unit/vpn/test_cli20_ipsecpolicy.py +++ b/neutronclient/tests/unit/vpn/test_cli20_ipsecpolicy.py @@ -16,6 +16,7 @@ import sys +from neutronclient.common import exceptions from neutronclient.neutron.v2_0.vpn import ipsecpolicy from neutronclient.tests.unit import test_cli20 @@ -98,7 +99,7 @@ class CLITestV20VpnIpsecPolicyJSON(test_cli20.CLITestV20Base): self._test_create_resource(resource, cmd, name, my_id, args, position_names, position_values) - def _test_lifetime_values(self, lifetime): + def _test_lifetime_values(self, lifetime, expected_exc=None): resource = 'ipsecpolicy' cmd = ipsecpolicy.CreateIPsecPolicy(test_cli20.MyApp(sys.stdout), None) name = 'ipsecpolicy1' @@ -131,19 +132,24 @@ class CLITestV20VpnIpsecPolicyJSON(test_cli20.CLITestV20Base): auth_algorithm, encryption_algorithm, phase1_negotiation_mode, ike_version, pfs, tenant_id] - try: - self._test_create_resource(resource, cmd, name, my_id, args, - position_names, position_values) - except Exception: - return - self.fail("IPsecPolicy Lifetime Error") + if not expected_exc: + expected_exc = exceptions.CommandError + self.assertRaises( + expected_exc, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) def test_create_ipsecpolicy_with_invalid_lifetime_keys(self): lifetime = 'uts=seconds,val=20000' + self._test_lifetime_values(lifetime, SystemExit) + + def test_create_ipsecpolicy_with_invalid_lifetime_units(self): + lifetime = 'units=minutes,value=600' self._test_lifetime_values(lifetime) - def test_create_ipsecpolicy_with_invalide_lifetime_values(self): - lifetime = 'units=minutes,value=0' + def test_create_ipsecpolicy_with_invalid_lifetime_value(self): + lifetime = 'units=seconds,value=0' self._test_lifetime_values(lifetime) def test_list_ipsecpolicy(self): From 8d4c0e4ac2e52af0e1ec4952f31aed851d1815e9 Mon Sep 17 00:00:00 2001 From: Miguel Lavalle Date: Sat, 20 Feb 2016 01:04:36 +0000 Subject: [PATCH 37/57] Add DNS integration support to the client DNS integration has been added to Neutron. This commit complements that by adding --dns-name and --dns-domain arguments to create and update commands of networks, ports and floating IPs. Change-Id: I7a6ec05b6d7483fceb35f586ac476e8713904b59 Closes-Bug: #1547736 --- neutronclient/neutron/v2_0/dns.py | 67 +++++++++++++++++++ neutronclient/neutron/v2_0/floatingip.py | 5 ++ neutronclient/neutron/v2_0/network.py | 5 ++ neutronclient/neutron/v2_0/port.py | 5 ++ .../tests/unit/test_cli20_floatingips.py | 15 +++++ .../tests/unit/test_cli20_network.py | 29 ++++++++ neutronclient/tests/unit/test_cli20_port.py | 30 +++++++++ 7 files changed, 156 insertions(+) create mode 100644 neutronclient/neutron/v2_0/dns.py diff --git a/neutronclient/neutron/v2_0/dns.py b/neutronclient/neutron/v2_0/dns.py new file mode 100644 index 0000000..0cf7ec0 --- /dev/null +++ b/neutronclient/neutron/v2_0/dns.py @@ -0,0 +1,67 @@ +# Copyright (c) 2016 IBM +# 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. + +from neutronclient._i18n import _ + + +def add_dns_argument_create(parser, resource, attribute): + # Add dns_name and dns_domain support to network, port and floatingip + # create + argument = '--dns-%s' % attribute + parser.add_argument( + argument, + help=_('Assign DNS %(attribute)s to the %(resource)s ' + '(requires DNS integration ' + 'extension)') % {'attribute': attribute, 'resource': resource}) + + +def args2body_dns_create(parsed_args, resource, attribute): + # Add dns_name and dns_domain support to network, port and floatingip + # create + destination = 'dns_%s' % attribute + argument = getattr(parsed_args, destination) + if argument: + resource[destination] = argument + + +def add_dns_argument_update(parser, resource, attribute): + # Add dns_name and dns_domain support to network, port and floatingip + # update + argument = '--dns-%s' % attribute + no_argument = '--no-dns-%s' % attribute + dns_args = parser.add_mutually_exclusive_group() + dns_args.add_argument( + argument, + help=_('Assign DNS %(attribute)s to the %(resource)s ' + '(requires DNS integration ' + 'extension.)') % {'attribute': attribute, 'resource': resource}) + dns_args.add_argument( + no_argument, action='store_true', + help=_('Unassign DNS %(attribute)s from the %(resource)s ' + '(requires DNS integration ' + 'extension.)') % {'attribute': attribute, 'resource': resource}) + + +def args2body_dns_update(parsed_args, resource, attribute): + # Add dns_name and dns_domain support to network, port and floatingip + # update + destination = 'dns_%s' % attribute + no_destination = 'no_dns_%s' % attribute + argument = getattr(parsed_args, destination) + no_argument = getattr(parsed_args, no_destination) + if argument: + resource[destination] = argument + if no_argument: + resource[destination] = "" diff --git a/neutronclient/neutron/v2_0/floatingip.py b/neutronclient/neutron/v2_0/floatingip.py index 7e634e9..e361a29 100644 --- a/neutronclient/neutron/v2_0/floatingip.py +++ b/neutronclient/neutron/v2_0/floatingip.py @@ -20,6 +20,7 @@ import argparse from neutronclient._i18n import _ from neutronclient.neutron import v2_0 as neutronV20 +from neutronclient.neutron.v2_0 import dns class ListFloatingIP(neutronV20.ListCommand): @@ -68,6 +69,8 @@ class CreateFloatingIP(neutronV20.CreateCommand): '--subnet', dest='subnet_id', help=_('Subnet ID on which you want to create the floating IP.')) + dns.add_dns_argument_create(parser, self.resource, 'domain') + dns.add_dns_argument_create(parser, self.resource, 'name') def args2body(self, parsed_args): _network_id = neutronV20.find_resourceid_by_name_or_id( @@ -77,6 +80,8 @@ class CreateFloatingIP(neutronV20.CreateCommand): ['port_id', 'tenant_id', 'fixed_ip_address', 'floating_ip_address', 'subnet_id']) + dns.args2body_dns_create(parsed_args, body, 'domain') + dns.args2body_dns_create(parsed_args, body, 'name') return {self.resource: body} diff --git a/neutronclient/neutron/v2_0/network.py b/neutronclient/neutron/v2_0/network.py index e5ac3bd..2fb26d3 100644 --- a/neutronclient/neutron/v2_0/network.py +++ b/neutronclient/neutron/v2_0/network.py @@ -21,6 +21,7 @@ from neutronclient.common import exceptions from neutronclient.common import utils from neutronclient.neutron import v2_0 as neutronV20 from neutronclient.neutron.v2_0 import availability_zone +from neutronclient.neutron.v2_0 import dns from neutronclient.neutron.v2_0.qos import policy as qos_policy @@ -165,6 +166,7 @@ class CreateNetwork(neutronV20.CreateCommand, qos_policy.CreateQosPolicyMixin): self.add_arguments_qos_policy(parser) availability_zone.add_az_hint_argument(parser, self.resource) + dns.add_dns_argument_create(parser, self.resource, 'domain') def args2body(self, parsed_args): body = {'name': parsed_args.name, @@ -178,6 +180,7 @@ class CreateNetwork(neutronV20.CreateCommand, qos_policy.CreateQosPolicyMixin): self.args2body_qos_policy(parsed_args, body) availability_zone.args2body_az_hint(parsed_args, body) + dns.args2body_dns_create(parsed_args, body, 'domain') return {'network': body} @@ -195,8 +198,10 @@ class UpdateNetwork(neutronV20.UpdateCommand, qos_policy.UpdateQosPolicyMixin): def add_known_arguments(self, parser): self.add_arguments_qos_policy(parser) + dns.add_dns_argument_update(parser, self.resource, 'domain') def args2body(self, parsed_args): body = {} self.args2body_qos_policy(parsed_args, body) + dns.args2body_dns_update(parsed_args, body, 'domain') return {'network': body} diff --git a/neutronclient/neutron/v2_0/port.py b/neutronclient/neutron/v2_0/port.py index 0e27cd2..29426ee 100644 --- a/neutronclient/neutron/v2_0/port.py +++ b/neutronclient/neutron/v2_0/port.py @@ -22,6 +22,7 @@ from neutronclient._i18n import _ from neutronclient.common import exceptions from neutronclient.common import utils from neutronclient.neutron import v2_0 as neutronV20 +from neutronclient.neutron.v2_0 import dns from neutronclient.neutron.v2_0.qos import policy as qos_policy @@ -263,6 +264,7 @@ class CreatePort(neutronV20.CreateCommand, UpdatePortSecGroupMixin, parser.add_argument( 'network_id', metavar='NETWORK', help=_('Network ID or name this port belongs to.')) + dns.add_dns_argument_create(parser, self.resource, 'name') def args2body(self, parsed_args): client = self.get_client() @@ -283,6 +285,7 @@ class CreatePort(neutronV20.CreateCommand, UpdatePortSecGroupMixin, self.args2body_extradhcpopt(parsed_args, body) self.args2body_qos_policy(parsed_args, body) self.args2body_allowedaddresspairs(parsed_args, body) + dns.args2body_dns_create(parsed_args, body, 'name') return {'port': body} @@ -314,6 +317,7 @@ class UpdatePort(neutronV20.UpdateCommand, UpdatePortSecGroupMixin, self.add_arguments_extradhcpopt(parser) self.add_arguments_qos_policy(parser) self.add_arguments_allowedaddresspairs(parser) + dns.add_dns_argument_update(parser, self.resource, 'name') def args2body(self, parsed_args): body = {} @@ -326,5 +330,6 @@ class UpdatePort(neutronV20.UpdateCommand, UpdatePortSecGroupMixin, self.args2body_extradhcpopt(parsed_args, body) self.args2body_qos_policy(parsed_args, body) self.args2body_allowedaddresspairs(parsed_args, body) + dns.args2body_dns_update(parsed_args, body, 'name') return {'port': body} diff --git a/neutronclient/tests/unit/test_cli20_floatingips.py b/neutronclient/tests/unit/test_cli20_floatingips.py index d500af8..cca3706 100644 --- a/neutronclient/tests/unit/test_cli20_floatingips.py +++ b/neutronclient/tests/unit/test_cli20_floatingips.py @@ -117,6 +117,21 @@ class CLITestV20FloatingIpsJSON(test_cli20.CLITestV20Base): self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) + def test_create_floatingip_with_dns_name_and_dns_domain(self): + # Create floatingip: fip1 with dns name and dns domain. + resource = 'floatingip' + cmd = fip.CreateFloatingIP(test_cli20.MyApp(sys.stdout), None) + name = 'fip1' + myid = 'myid' + dns_name_name = 'my-floatingip' + dns_domain_name = 'my-domain.org.' + args = [name, '--dns-name', dns_name_name, '--dns-domain', + dns_domain_name] + position_names = ['floating_network_id', 'dns_name', 'dns_domain'] + position_values = [name, dns_name_name, dns_domain_name] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + def test_list_floatingips(self): # list floatingips: -D. resources = 'floatingips' diff --git a/neutronclient/tests/unit/test_cli20_network.py b/neutronclient/tests/unit/test_cli20_network.py index 0465b88..dc8fd72 100644 --- a/neutronclient/tests/unit/test_cli20_network.py +++ b/neutronclient/tests/unit/test_cli20_network.py @@ -163,6 +163,19 @@ class CLITestV20NetworkJSON(test_cli20.CLITestV20Base): self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) + def test_create_network_with_dns_domain(self): + # Create net: --dns-domain my-domain.org. + resource = 'network' + cmd = network.CreateNetwork(test_cli20.MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + dns_domain_name = 'my-domain.org.' + args = [name, '--dns-domain', dns_domain_name] + position_names = ['name', 'dns_domain'] + position_values = [name, dns_domain_name] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + def test_list_nets_empty_with_column(self): resources = "networks" cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) @@ -528,6 +541,22 @@ class CLITestV20NetworkJSON(test_cli20.CLITestV20Base): ['myid', '--no-qos-policy'], {'qos_policy_id': None, }) + def test_update_network_with_dns_domain(self): + # Update net: myid --dns-domain my-domain.org. + resource = 'network' + cmd = network.UpdateNetwork(test_cli20.MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--dns-domain', 'my-domain.org.'], + {'dns_domain': 'my-domain.org.', }) + + def test_update_network_with_no_dns_domain(self): + # Update net: myid --no-dns-domain + resource = 'network' + cmd = network.UpdateNetwork(test_cli20.MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--no-dns-domain'], + {'dns_domain': "", }) + def test_show_network(self): # Show net: --fields id --fields name myid. resource = 'network' diff --git a/neutronclient/tests/unit/test_cli20_port.py b/neutronclient/tests/unit/test_cli20_port.py index 4b6d617..8f502eb 100644 --- a/neutronclient/tests/unit/test_cli20_port.py +++ b/neutronclient/tests/unit/test_cli20_port.py @@ -338,6 +338,20 @@ class CLITestV20PortJSON(test_cli20.CLITestV20Base): self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) + def test_create_port_with_dns_name(self): + # Create port: --dns-name my-port. + resource = 'port' + cmd = port.CreatePort(test_cli20.MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + dns_name_name = 'my-port' + args = [netid, '--dns-name', dns_name_name] + position_names = ['network_id', 'dns_name'] + position_values = [netid, dns_name_name] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + def test_create_port_with_allowed_address_pair_ipaddr(self): # Create port: # --allowed-address-pair ip_address=addr0 @@ -648,6 +662,22 @@ class CLITestV20PortJSON(test_cli20.CLITestV20Base): ['myid', '--no-qos-policy'], {'qos_policy_id': None, }) + def test_update_port_with_dns_name(self): + # Update port: myid --dns-name my-port. + resource = 'port' + cmd = port.UpdatePort(test_cli20.MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--dns-name', 'my-port'], + {'dns_name': 'my-port', }) + + def test_update_port_with_no_dns_name(self): + # Update port: myid --no-dns-name + resource = 'port' + cmd = port.UpdatePort(test_cli20.MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--no-dns-name'], + {'dns_name': "", }) + def test_delete_extra_dhcp_opts_from_port(self): resource = 'port' myid = 'myid' From 0dd54a08c63195046eede744b2d7051eaafae5fc Mon Sep 17 00:00:00 2001 From: Brandon Palm Date: Tue, 23 Feb 2016 14:06:12 -0600 Subject: [PATCH 38/57] Use instanceof instead of type Adjusted conditional statements to use instanceof when comparing variables. Instanceof supports inheritance type checking better than type. Change-Id: I873ef7d5e283ee70f1548f040f1c1d9a675c7159 Closes-Bug: 1548974 --- neutronclient/neutron/v2_0/__init__.py | 2 +- neutronclient/v2_0/client.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index 2166775..f3f4669 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -351,7 +351,7 @@ def _merge_args(qCmd, parsed_args, _extra_values, value_specs): if isinstance(arg_value, list): if value and isinstance(value, list): if (not arg_value or - type(arg_value[0]) == type(value[0])): + isinstance(arg_value[0], type(value[0]))): arg_value.extend(value) _extra_values.pop(key) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index cc323c9..69cc64f 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -185,7 +185,7 @@ class ClientBase(object): # Add format and tenant_id action += ".%s" % self.format action = self.action_prefix + action - if type(params) is dict and params: + if isinstance(params, dict) and params: params = utils.safe_encode_dict(params) action += '?' + urlparse.urlencode(params, doseq=1) @@ -216,7 +216,7 @@ class ClientBase(object): """ if data is None: return None - elif type(data) is dict: + elif isinstance(data, dict): return serializer.Serializer().serialize(data) else: raise Exception(_("Unable to serialize object of type = '%s'") % From af1a55bfd2e47b0e3cd8349f0a9b1277474fee18 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Fri, 4 Dec 2015 01:53:58 +0900 Subject: [PATCH 39/57] Ensure to use exception per status code for all cases Previously, only when an exception has a content with {'NeutronError': {'type': xxxx, 'message': xxxx}}, exception per status code is raised from neutronclient library. There are cases where this kind of message is not contained in exception messages, for example, some extension is loaded. Library users expect an exception is raised based on response status code and it should not depend on an exception message. This commit applies a fallback logic to map generic per-status exception to all exception types from the neutron server. Closes-Bug: #1513879 Change-Id: Ib3d0a8359aed444b12217b3404d40443d61fc2c0 --- neutronclient/tests/unit/test_cli20.py | 32 +++++++++++-------- neutronclient/v2_0/client.py | 44 +++++++++++--------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/neutronclient/tests/unit/test_cli20.py b/neutronclient/tests/unit/test_cli20.py index 8aa0aab..e3a1014 100644 --- a/neutronclient/tests/unit/test_cli20.py +++ b/neutronclient/tests/unit/test_cli20.py @@ -713,6 +713,8 @@ class CLITestV20ExceptionHandler(CLITestV20Base): client.exception_handler_v20, status_code, error_content) self.assertEqual(status_code, e.status_code) + self.assertEqual(expected_exception.__name__, + e.__class__.__name__) if expected_msg is None: if error_detail: @@ -780,32 +782,36 @@ class CLITestV20ExceptionHandler(CLITestV20Base): 'UnknownError', error_msg, error_detail) def test_exception_handler_v20_bad_neutron_error(self): - error_content = {'NeutronError': {'unknown_key': 'UNKNOWN'}} - self._test_exception_handler_v20( - exceptions.NeutronClientException, 500, - expected_msg={'unknown_key': 'UNKNOWN'}, - error_content=error_content) + for status_code, client_exc in exceptions.HTTP_EXCEPTION_MAP.items(): + error_content = {'NeutronError': {'unknown_key': 'UNKNOWN'}} + self._test_exception_handler_v20( + client_exc, status_code, + expected_msg="{'unknown_key': 'UNKNOWN'}", + error_content=error_content) def test_exception_handler_v20_error_dict_contains_message(self): error_content = {'message': 'This is an error message'} - self._test_exception_handler_v20( - exceptions.NeutronClientException, 500, - expected_msg='This is an error message', - error_content=error_content) + for status_code, client_exc in exceptions.HTTP_EXCEPTION_MAP.items(): + self._test_exception_handler_v20( + client_exc, status_code, + expected_msg='This is an error message', + error_content=error_content) def test_exception_handler_v20_error_dict_not_contain_message(self): + # 599 is not contained in HTTP_EXCEPTION_MAP. error_content = {'error': 'This is an error message'} - expected_msg = '%s-%s' % (500, error_content) + expected_msg = '%s-%s' % (599, error_content) self._test_exception_handler_v20( - exceptions.NeutronClientException, 500, + exceptions.NeutronClientException, 599, expected_msg=expected_msg, error_content=error_content) def test_exception_handler_v20_default_fallback(self): + # 599 is not contained in HTTP_EXCEPTION_MAP. error_content = 'This is an error message' - expected_msg = '%s-%s' % (500, error_content) + expected_msg = '%s-%s' % (599, error_content) self._test_exception_handler_v20( - exceptions.NeutronClientException, 500, + exceptions.NeutronClientException, 599, expected_msg=expected_msg, error_content=error_content) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index cc323c9..08a8503 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -47,7 +47,7 @@ def exception_handler_v20(status_code, error_content): if isinstance(error_content, dict): error_dict = error_content.get('NeutronError') # Find real error type - bad_neutron_error_flag = False + client_exc = None if error_dict: # If Neutron key is found, it will definitely contain # a 'message' and 'type' keys? @@ -56,35 +56,29 @@ def exception_handler_v20(status_code, error_content): error_message = error_dict['message'] if error_dict['detail']: error_message += "\n" + error_dict['detail'] - except Exception: - bad_neutron_error_flag = True - if not bad_neutron_error_flag: # If corresponding exception is defined, use it. client_exc = getattr(exceptions, '%sClient' % error_type, None) - # Otherwise look up per status-code client exception - if not client_exc: - client_exc = exceptions.HTTP_EXCEPTION_MAP.get(status_code) - if client_exc: - raise client_exc(message=error_message, - status_code=status_code) - else: - raise exceptions.NeutronClientException( - status_code=status_code, message=error_message) - else: - raise exceptions.NeutronClientException(status_code=status_code, - message=error_dict) + except Exception: + error_message = "%s" % error_dict else: - message = None + error_message = None if isinstance(error_content, dict): - message = error_content.get('message') - if message: - raise exceptions.NeutronClientException(status_code=status_code, - message=message) + error_message = error_content.get('message') + if not error_message: + # If we end up here the exception was not a neutron error + error_message = "%s-%s" % (status_code, error_content) - # If we end up here the exception was not a neutron error - msg = "%s-%s" % (status_code, error_content) - raise exceptions.NeutronClientException(status_code=status_code, - message=msg) + # If an exception corresponding to the error type is not found, + # look up per status-code client exception. + if not client_exc: + client_exc = exceptions.HTTP_EXCEPTION_MAP.get(status_code) + # If there is no exception per status-code, + # Use NeutronClientException as fallback. + if not client_exc: + client_exc = exceptions.NeutronClientException + + raise client_exc(message=error_message, + status_code=status_code) class APIParamsCall(object): From e0667e96cfb9dedcdcf0eb5bbafe6efb4cf5c437 Mon Sep 17 00:00:00 2001 From: Carl Baldwin Date: Fri, 19 Feb 2016 23:28:49 +0000 Subject: [PATCH 40/57] Add use_default_subnetpool to subnet create requests Change-Id: I7dcbc8477b2ffa776a9bb272b896a5adfca860ae Depends-On: Ifff57c0485e4727f352b2cc2bd1bdaabd0f1606b Closes-Bug: #1547705 --- neutronclient/neutron/v2_0/subnet.py | 6 ++++++ neutronclient/tests/unit/test_cli20_subnet.py | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/neutronclient/neutron/v2_0/subnet.py b/neutronclient/neutron/v2_0/subnet.py index d8b5946..3ba4f17 100644 --- a/neutronclient/neutron/v2_0/subnet.py +++ b/neutronclient/neutron/v2_0/subnet.py @@ -187,6 +187,10 @@ class CreateSubnet(neutronV20.CreateCommand): '--subnetpool', metavar='SUBNETPOOL', help=_('ID or name of subnetpool from which this subnet ' 'will obtain a CIDR.')) + parser.add_argument( + '--use-default-subnetpool', + action='store_true', + help=_('Use default subnetpool for ip_version, if it exists.')) parser.add_argument( '--prefixlen', metavar='PREFIX_LENGTH', help=_('Prefix length for subnet allocation from subnetpool.')) @@ -199,6 +203,8 @@ class CreateSubnet(neutronV20.CreateCommand): if parsed_args.prefixlen: body['prefixlen'] = parsed_args.prefixlen ip_version = parsed_args.ip_version + if parsed_args.use_default_subnetpool: + body['use_default_subnetpool'] = True if parsed_args.subnetpool: if parsed_args.subnetpool == 'None': _subnetpool_id = None diff --git a/neutronclient/tests/unit/test_cli20_subnet.py b/neutronclient/tests/unit/test_cli20_subnet.py index 4a28198..a1ebf89 100644 --- a/neutronclient/tests/unit/test_cli20_subnet.py +++ b/neutronclient/tests/unit/test_cli20_subnet.py @@ -290,6 +290,25 @@ class CLITestV20SubnetJSON(test_cli20.CLITestV20Base): position_names, position_values, tenant_id='tenantid') + def test_create_subnet_with_use_default_subnetpool(self): + # Create subnet: --tenant-id tenantid --use-default-subnetpool \ + # netid cidr. + resource = 'subnet' + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'prefixvalue' + args = ['--tenant_id', 'tenantid', + '--use-default-subnetpool', + netid, cidr] + position_names = ['ip_version', 'use_default_subnetpool', 'network_id', + 'cidr'] + position_values = [4, True, netid, cidr] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + def test_create_subnet_with_disable_dhcp(self): # Create subnet: --tenant-id tenantid --disable-dhcp netid cidr. resource = 'subnet' From 3c26455a03e1144d582007f24c61be0100bb25a6 Mon Sep 17 00:00:00 2001 From: "vikram.choudhary" Date: Thu, 11 Feb 2016 15:10:11 +0530 Subject: [PATCH 41/57] BGP Dynamic Routing: neutronclient changes This patch adds neutronclient support for BGP routing functionality. Partially-Implements: blueprint bgp-dynamic-routing Co-Authored-By: Ryan Tidwell Co-Authored-By: Numan Siddique Co-Authored-By: Jaume Devesa Change-Id: I5b20bcbf6c837495d81c395f600498d2c8f3495c --- neutronclient/neutron/v2_0/bgp/__init__.py | 0 .../neutron/v2_0/bgp/dragentscheduler.py | 117 ++++++++ neutronclient/neutron/v2_0/bgp/peer.py | 127 ++++++++ neutronclient/neutron/v2_0/bgp/speaker.py | 277 ++++++++++++++++++ neutronclient/shell.py | 32 ++ neutronclient/tests/unit/bgp/__init__.py | 0 .../unit/bgp/test_cli20_dragentscheduler.py | 66 +++++ .../tests/unit/bgp/test_cli20_peer.py | 223 ++++++++++++++ .../tests/unit/bgp/test_cli20_speaker.py | 267 +++++++++++++++++ neutronclient/v2_0/client.py | 117 ++++++++ .../bgp-dynamic-routing-b97a1c81d3007049.yaml | 5 + 11 files changed, 1231 insertions(+) create mode 100644 neutronclient/neutron/v2_0/bgp/__init__.py create mode 100644 neutronclient/neutron/v2_0/bgp/dragentscheduler.py create mode 100644 neutronclient/neutron/v2_0/bgp/peer.py create mode 100755 neutronclient/neutron/v2_0/bgp/speaker.py create mode 100644 neutronclient/tests/unit/bgp/__init__.py create mode 100644 neutronclient/tests/unit/bgp/test_cli20_dragentscheduler.py create mode 100644 neutronclient/tests/unit/bgp/test_cli20_peer.py create mode 100644 neutronclient/tests/unit/bgp/test_cli20_speaker.py create mode 100644 releasenotes/notes/bgp-dynamic-routing-b97a1c81d3007049.yaml diff --git a/neutronclient/neutron/v2_0/bgp/__init__.py b/neutronclient/neutron/v2_0/bgp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neutronclient/neutron/v2_0/bgp/dragentscheduler.py b/neutronclient/neutron/v2_0/bgp/dragentscheduler.py new file mode 100644 index 0000000..81ce8f1 --- /dev/null +++ b/neutronclient/neutron/v2_0/bgp/dragentscheduler.py @@ -0,0 +1,117 @@ +# Copyright 2016 Huawei Technologies India Pvt. Ltd. +# 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. +# + +from __future__ import print_function + +from neutronclient._i18n import _ +from neutronclient.neutron import v2_0 as neutronV20 +from neutronclient.neutron.v2_0.bgp import speaker as bgp_speaker + + +def add_common_args(parser): + parser.add_argument('dragent_id', + metavar='BGP_DRAGENT_ID', + help=_('ID of the Dynamic Routing agent.')) + parser.add_argument('bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + + +class AddBGPSpeakerToDRAgent(neutronV20.NeutronCommand): + """Add a BGP speaker to a Dynamic Routing agent.""" + + def get_parser(self, prog_name): + parser = super(AddBGPSpeakerToDRAgent, self).get_parser(prog_name) + add_common_args(parser) + return parser + + def take_action(self, parsed_args): + neutron_client = self.get_client() + _speaker_id = bgp_speaker.get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + neutron_client.add_bgp_speaker_to_dragent( + parsed_args.dragent_id, {'bgp_speaker_id': _speaker_id}) + print(_('Associated BGP speaker %s to the Dynamic Routing agent.') + % parsed_args.bgp_speaker, file=self.app.stdout) + + +class RemoveBGPSpeakerFromDRAgent(neutronV20.NeutronCommand): + """Removes a BGP speaker from a Dynamic Routing agent.""" + + def get_parser(self, prog_name): + parser = super(RemoveBGPSpeakerFromDRAgent, self).get_parser( + prog_name) + add_common_args(parser) + return parser + + def take_action(self, parsed_args): + neutron_client = self.get_client() + _speaker_id = bgp_speaker.get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + neutron_client.remove_bgp_speaker_from_dragent(parsed_args.dragent_id, + _speaker_id) + print(_('Disassociated BGP speaker %s from the ' + 'Dynamic Routing agent.') + % parsed_args.bgp_speaker, file=self.app.stdout) + + +class ListBGPSpeakersOnDRAgent(neutronV20.ListCommand): + """List BGP speakers hosted by a Dynamic Routing agent.""" + + list_columns = ['id', 'name', 'local_as', 'ip_version'] + resource = 'bgp_speaker' + + def get_parser(self, prog_name): + parser = super(ListBGPSpeakersOnDRAgent, + self).get_parser(prog_name) + parser.add_argument( + 'dragent_id', + metavar='BGP_DRAGENT_ID', + help=_('ID of the Dynamic Routing agent.')) + return parser + + def call_server(self, neutron_client, search_opts, parsed_args): + data = neutron_client.list_bgp_speaker_on_dragent( + parsed_args.dragent_id, **search_opts) + return data + + +class ListDRAgentsHostingBGPSpeaker(neutronV20.ListCommand): + """List Dynamic Routing agents hosting a BGP speaker.""" + + resource = 'agent' + _formatters = {} + list_columns = ['id', 'host', 'admin_state_up', 'alive'] + unknown_parts_flag = False + + def get_parser(self, prog_name): + parser = super(ListDRAgentsHostingBGPSpeaker, + self).get_parser(prog_name) + parser.add_argument('bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + return parser + + def extend_list(self, data, parsed_args): + for agent in data: + agent['alive'] = ":-)" if agent['alive'] else 'xxx' + + def call_server(self, neutron_client, search_opts, parsed_args): + _speaker_id = bgp_speaker.get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + search_opts['bgp_speaker'] = _speaker_id + data = neutron_client.list_dragents_hosting_bgp_speaker(**search_opts) + return data diff --git a/neutronclient/neutron/v2_0/bgp/peer.py b/neutronclient/neutron/v2_0/bgp/peer.py new file mode 100644 index 0000000..8fefb66 --- /dev/null +++ b/neutronclient/neutron/v2_0/bgp/peer.py @@ -0,0 +1,127 @@ +# Copyright 2016 Huawei Technologies India Pvt. Ltd. +# 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. +# + +from neutronclient._i18n import _ +from neutronclient.common import exceptions +from neutronclient.common import utils +from neutronclient.common import validators +from neutronclient.neutron import v2_0 as neutronv20 + + +def get_bgp_peer_id(client, id_or_name): + return neutronv20.find_resourceid_by_name_or_id(client, + 'bgp_peer', + id_or_name) + + +def validate_peer_attributes(parsed_args): + # Validate AS number + validators.validate_int_range(parsed_args, 'remote_as', + neutronv20.bgp.speaker.MIN_AS_NUM, + neutronv20.bgp.speaker.MAX_AS_NUM) + # Validate password + if parsed_args.auth_type != 'none' and parsed_args.password is None: + raise exceptions.CommandError(_('Must provide password if auth-type ' + 'is specified.')) + if parsed_args.auth_type == 'none' and parsed_args.password: + raise exceptions.CommandError(_('Must provide auth-type if password ' + 'is specified.')) + + +class ListPeers(neutronv20.ListCommand): + """List BGP peers.""" + + resource = 'bgp_peer' + list_columns = ['id', 'name', 'peer_ip', 'remote_as'] + pagination_support = True + sorting_support = True + + +class ShowPeer(neutronv20.ShowCommand): + """Show information of a given BGP peer.""" + + resource = 'bgp_peer' + + +class CreatePeer(neutronv20.CreateCommand): + """Create a BGP Peer.""" + + resource = 'bgp_peer' + + def add_known_arguments(self, parser): + parser.add_argument( + 'name', + metavar='NAME', + help=_('Name of the BGP peer to create.')) + parser.add_argument( + '--peer-ip', + metavar='PEER_IP_ADDRESS', + required=True, + help=_('Peer IP address.')) + parser.add_argument( + '--remote-as', + required=True, + metavar='PEER_REMOTE_AS', + help=_('Peer AS number. (Integer in [%(min_val)s, %(max_val)s] ' + 'is allowed.)') % + {'min_val': neutronv20.bgp.speaker.MIN_AS_NUM, + 'max_val': neutronv20.bgp.speaker.MAX_AS_NUM}) + parser.add_argument( + '--auth-type', + metavar='PEER_AUTH_TYPE', + choices=['none', 'md5'], + default='none', + type=utils.convert_to_lowercase, + help=_('Authentication algorithm. Supported algorithms: ' + 'none(default), md5')) + parser.add_argument( + '--password', + metavar='AUTH_PASSWORD', + help=_('Authentication password.')) + + def args2body(self, parsed_args): + body = {} + validate_peer_attributes(parsed_args) + neutronv20.update_dict(parsed_args, body, + ['name', 'peer_ip', + 'remote_as', 'auth_type', 'password']) + return {self.resource: body} + + +class UpdatePeer(neutronv20.UpdateCommand): + """Update BGP Peer's information.""" + + resource = 'bgp_peer' + + def add_known_arguments(self, parser): + parser.add_argument( + '--name', + help=_('Updated name of the BGP peer.')) + parser.add_argument( + '--password', + metavar='AUTH_PASSWORD', + help=_('Updated authentication password.')) + + def args2body(self, parsed_args): + body = {} + neutronv20.update_dict(parsed_args, body, ['name', 'password']) + return {self.resource: body} + + +class DeletePeer(neutronv20.DeleteCommand): + """Delete a BGP peer.""" + + resource = 'bgp_peer' diff --git a/neutronclient/neutron/v2_0/bgp/speaker.py b/neutronclient/neutron/v2_0/bgp/speaker.py new file mode 100755 index 0000000..f2a3df7 --- /dev/null +++ b/neutronclient/neutron/v2_0/bgp/speaker.py @@ -0,0 +1,277 @@ +# Copyright 2016 Huawei Technologies India Pvt. Ltd. +# 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. +# + +from __future__ import print_function + +from neutronclient._i18n import _ +from neutronclient.common import utils +from neutronclient.common import validators +from neutronclient.neutron import v2_0 as neutronv20 +from neutronclient.neutron.v2_0.bgp import peer as bgp_peer + +# Allowed BGP Autonomous number range +MIN_AS_NUM = 1 +MAX_AS_NUM = 65535 + + +def get_network_id(client, id_or_name): + return neutronv20.find_resourceid_by_name_or_id(client, + 'network', + id_or_name) + + +def get_bgp_speaker_id(client, id_or_name): + return neutronv20.find_resourceid_by_name_or_id(client, + 'bgp_speaker', + id_or_name) + + +def validate_speaker_attributes(parsed_args): + # Validate AS number + validators.validate_int_range(parsed_args, 'local_as', + MIN_AS_NUM, MAX_AS_NUM) + + +def add_common_arguments(parser): + utils.add_boolean_argument( + parser, '--advertise-floating-ip-host-routes', + help=_('Whether to enable or disable the advertisement ' + 'of floating-ip host routes by the BGP speaker. ' + 'By default floating ip host routes will be ' + 'advertised by the BGP speaker.')) + utils.add_boolean_argument( + parser, '--advertise-tenant-networks', + help=_('Whether to enable or disable the advertisement ' + 'of tenant network routes by the BGP speaker. ' + 'By default tenant network routes will be ' + 'advertised by the BGP speaker.')) + + +def args2body_common_arguments(body, parsed_args): + neutronv20.update_dict(parsed_args, body, + ['name', + 'advertise_floating_ip_host_routes', + 'advertise_tenant_networks']) + + +class ListSpeakers(neutronv20.ListCommand): + """List BGP speakers.""" + + resource = 'bgp_speaker' + list_columns = ['id', 'name', 'local_as', 'ip_version'] + pagination_support = True + sorting_support = True + + +class ShowSpeaker(neutronv20.ShowCommand): + """Show information of a given BGP speaker.""" + + resource = 'bgp_speaker' + + +class CreateSpeaker(neutronv20.CreateCommand): + """Create a BGP Speaker.""" + + resource = 'bgp_speaker' + + def add_known_arguments(self, parser): + parser.add_argument( + 'name', + metavar='NAME', + help=_('Name of the BGP speaker to create.')) + parser.add_argument( + '--local-as', + metavar='LOCAL_AS', + required=True, + help=_('Local AS number. (Integer in [%(min_val)s, %(max_val)s] ' + 'is allowed.)') % {'min_val': MIN_AS_NUM, + 'max_val': MAX_AS_NUM}) + parser.add_argument( + '--ip-version', + type=int, choices=[4, 6], + default=4, + help=_('IP version for the BGP speaker (default is 4).')) + add_common_arguments(parser) + + def args2body(self, parsed_args): + body = {} + validate_speaker_attributes(parsed_args) + body['local_as'] = parsed_args.local_as + body['ip_version'] = parsed_args.ip_version + args2body_common_arguments(body, parsed_args) + return {self.resource: body} + + +class UpdateSpeaker(neutronv20.UpdateCommand): + """Update BGP Speaker's information.""" + + resource = 'bgp_speaker' + + def add_known_arguments(self, parser): + parser.add_argument( + '--name', + help=_('Name of the BGP speaker to update.')) + add_common_arguments(parser) + + def args2body(self, parsed_args): + body = {} + args2body_common_arguments(body, parsed_args) + return {self.resource: body} + + +class DeleteSpeaker(neutronv20.DeleteCommand): + """Delete a BGP speaker.""" + + resource = 'bgp_speaker' + + +class AddPeerToSpeaker(neutronv20.NeutronCommand): + """Add a peer to the BGP speaker.""" + + def get_parser(self, prog_name): + parser = super(AddPeerToSpeaker, self).get_parser(prog_name) + parser.add_argument( + 'bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + parser.add_argument( + 'bgp_peer', + metavar='BGP_PEER', + help=_('ID or name of the BGP peer to add.')) + return parser + + def run(self, parsed_args): + neutron_client = self.get_client() + _speaker_id = get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + _peer_id = bgp_peer.get_bgp_peer_id(neutron_client, + parsed_args.bgp_peer) + neutron_client.add_peer_to_bgp_speaker(_speaker_id, + {'bgp_peer_id': _peer_id}) + print(_('Added BGP peer %(peer)s to BGP speaker %(speaker)s.') % + {'peer': parsed_args.bgp_peer, + 'speaker': parsed_args.bgp_speaker}, + file=self.app.stdout) + + +class RemovePeerFromSpeaker(neutronv20.NeutronCommand): + """Remove a peer from the BGP speaker.""" + + def get_parser(self, prog_name): + parser = super(RemovePeerFromSpeaker, self).get_parser(prog_name) + parser.add_argument( + 'bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + parser.add_argument( + 'bgp_peer', + metavar='BGP_PEER', + help=_('ID or name of the BGP peer to remove.')) + return parser + + def run(self, parsed_args): + neutron_client = self.get_client() + _speaker_id = get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + _peer_id = bgp_peer.get_bgp_peer_id(neutron_client, + parsed_args.bgp_peer) + neutron_client.remove_peer_from_bgp_speaker(_speaker_id, + {'bgp_peer_id': _peer_id}) + print(_('Removed BGP peer %(peer)s from BGP speaker %(speaker)s.') % + {'peer': parsed_args.bgp_peer, + 'speaker': parsed_args.bgp_speaker}, + file=self.app.stdout) + + +class AddNetworkToSpeaker(neutronv20.NeutronCommand): + """Add a network to the BGP speaker.""" + + def get_parser(self, prog_name): + parser = super(AddNetworkToSpeaker, self).get_parser(prog_name) + parser.add_argument( + 'bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + parser.add_argument( + 'network', + metavar='NETWORK', + help=_('ID or name of the network to add.')) + return parser + + def run(self, parsed_args): + neutron_client = self.get_client() + _speaker_id = get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + _net_id = get_network_id(neutron_client, + parsed_args.network) + neutron_client.add_network_to_bgp_speaker(_speaker_id, + {'network_id': _net_id}) + print(_('Added network %(net)s to BGP speaker %(speaker)s.') % + {'net': parsed_args.network, 'speaker': parsed_args.bgp_speaker}, + file=self.app.stdout) + + +class RemoveNetworkFromSpeaker(neutronv20.NeutronCommand): + """Remove a network from the BGP speaker.""" + + def get_parser(self, prog_name): + parser = super(RemoveNetworkFromSpeaker, self).get_parser(prog_name) + parser.add_argument( + 'bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + parser.add_argument( + 'network', + metavar='NETWORK', + help=_('ID or name of the network to remove.')) + return parser + + def run(self, parsed_args): + neutron_client = self.get_client() + _speaker_id = get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + _net_id = get_network_id(neutron_client, + parsed_args.network) + neutron_client.remove_network_from_bgp_speaker(_speaker_id, + {'network_id': _net_id}) + print(_('Removed network %(net)s from BGP speaker %(speaker)s.') % + {'net': parsed_args.network, 'speaker': parsed_args.bgp_speaker}, + file=self.app.stdout) + + +class ListRoutesAdvertisedBySpeaker(neutronv20.ListCommand): + """List routes advertised by a given BGP speaker.""" + + list_columns = ['id', 'destination', 'next_hop'] + resource = 'advertised_route' + pagination_support = True + sorting_support = True + + def get_parser(self, prog_name): + parser = super(ListRoutesAdvertisedBySpeaker, + self).get_parser(prog_name) + parser.add_argument( + 'bgp_speaker', + metavar='BGP_SPEAKER', + help=_('ID or name of the BGP speaker.')) + return parser + + def call_server(self, neutron_client, search_opts, parsed_args): + _speaker_id = get_bgp_speaker_id(neutron_client, + parsed_args.bgp_speaker) + data = neutron_client.list_route_advertised_from_bgp_speaker( + _speaker_id, **search_opts) + return data diff --git a/neutronclient/shell.py b/neutronclient/shell.py index c1f6b0a..795571a 100644 --- a/neutronclient/shell.py +++ b/neutronclient/shell.py @@ -45,6 +45,9 @@ from neutronclient.neutron.v2_0 import agent from neutronclient.neutron.v2_0 import agentscheduler from neutronclient.neutron.v2_0 import auto_allocated_topology from neutronclient.neutron.v2_0 import availability_zone +from neutronclient.neutron.v2_0.bgp import dragentscheduler as bgp_drsched +from neutronclient.neutron.v2_0.bgp import peer as bgp_peer +from neutronclient.neutron.v2_0.bgp import speaker as bgp_speaker from neutronclient.neutron.v2_0 import extension from neutronclient.neutron.v2_0.flavor import flavor from neutronclient.neutron.v2_0.flavor import flavor_profile @@ -395,6 +398,35 @@ COMMAND_V2 = { 'availability-zone-list': availability_zone.ListAvailabilityZone, 'auto-allocated-topology-show': ( auto_allocated_topology.ShowAutoAllocatedTopology), + 'bgp-dragent-speaker-add': ( + bgp_drsched.AddBGPSpeakerToDRAgent + ), + 'bgp-dragent-speaker-remove': ( + bgp_drsched.RemoveBGPSpeakerFromDRAgent + ), + 'bgp-speaker-list-on-dragent': ( + bgp_drsched.ListBGPSpeakersOnDRAgent + ), + 'bgp-dragent-list-hosting-speaker': ( + bgp_drsched.ListDRAgentsHostingBGPSpeaker + ), + 'bgp-speaker-list': bgp_speaker.ListSpeakers, + 'bgp-speaker-advertiseroute-list': ( + bgp_speaker.ListRoutesAdvertisedBySpeaker + ), + 'bgp-speaker-show': bgp_speaker.ShowSpeaker, + 'bgp-speaker-create': bgp_speaker.CreateSpeaker, + 'bgp-speaker-update': bgp_speaker.UpdateSpeaker, + 'bgp-speaker-delete': bgp_speaker.DeleteSpeaker, + 'bgp-speaker-peer-add': bgp_speaker.AddPeerToSpeaker, + 'bgp-speaker-peer-remove': bgp_speaker.RemovePeerFromSpeaker, + 'bgp-speaker-network-add': bgp_speaker.AddNetworkToSpeaker, + 'bgp-speaker-network-remove': bgp_speaker.RemoveNetworkFromSpeaker, + 'bgp-peer-list': bgp_peer.ListPeers, + 'bgp-peer-show': bgp_peer.ShowPeer, + 'bgp-peer-create': bgp_peer.CreatePeer, + 'bgp-peer-update': bgp_peer.UpdatePeer, + 'bgp-peer-delete': bgp_peer.DeletePeer, } COMMANDS = {'2.0': COMMAND_V2} diff --git a/neutronclient/tests/unit/bgp/__init__.py b/neutronclient/tests/unit/bgp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neutronclient/tests/unit/bgp/test_cli20_dragentscheduler.py b/neutronclient/tests/unit/bgp/test_cli20_dragentscheduler.py new file mode 100644 index 0000000..cbe85d9 --- /dev/null +++ b/neutronclient/tests/unit/bgp/test_cli20_dragentscheduler.py @@ -0,0 +1,66 @@ +# Copyright 2016 Huawei Technologies India Pvt. Ltd. +# 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 sys + +from neutronclient.neutron.v2_0.bgp import dragentscheduler as bgp_drsched +from neutronclient.tests.unit import test_cli20 +from neutronclient.tests.unit import test_cli20_agentschedulers as test_as + + +BGP_DRAGENT_ID = 'bgp_dragent_id1' +BGP_SPEAKER = 'bgp_speaker_id1' + + +class CLITestV20DRAgentScheduler(test_as.CLITestV20AgentScheduler): + + def test_add_bgp_speaker_to_dragent(self): + resource = 'agent' + cmd = bgp_drsched.AddBGPSpeakerToDRAgent( + test_cli20.MyApp(sys.stdout), None) + args = (BGP_DRAGENT_ID, BGP_SPEAKER) + body = {'bgp_speaker_id': BGP_SPEAKER} + result = {'bgp_speaker_id': 'bgp_speaker_id', } + self._test_add_to_agent(resource, cmd, args, + self.client.BGP_DRINSTANCES, + body, result) + + def test_remove_bgp_speaker_from_dragent(self): + resource = 'agent' + cmd = bgp_drsched.RemoveBGPSpeakerFromDRAgent( + test_cli20.MyApp(sys.stdout), None) + args = (BGP_DRAGENT_ID, BGP_SPEAKER) + self._test_remove_from_agent(resource, cmd, args, + self.client.BGP_DRINSTANCES) + + def test_list_bgp_speakers_on_dragent(self): + resources = 'bgp_speakers' + cmd = bgp_drsched.ListBGPSpeakersOnDRAgent( + test_cli20.MyApp(sys.stdout), None) + path = ((self.client.agent_path + self.client.BGP_DRINSTANCES) % + BGP_DRAGENT_ID) + self._test_list_resources(resources, cmd, base_args=[BGP_DRAGENT_ID], + path=path) + + def test_list_dragents_hosting_bgp_speaker(self): + resources = 'agent' + cmd = bgp_drsched.ListDRAgentsHostingBGPSpeaker( + test_cli20.MyApp(sys.stdout), None) + path = ((self.client.bgp_speaker_path + self.client.BGP_DRAGENTS) % + BGP_DRAGENT_ID) + contents = {self.id_field: 'myid1', 'alive': True} + self._test_list_resources(resources, cmd, base_args=[BGP_DRAGENT_ID], + path=path, response_contents=contents) diff --git a/neutronclient/tests/unit/bgp/test_cli20_peer.py b/neutronclient/tests/unit/bgp/test_cli20_peer.py new file mode 100644 index 0000000..b165327 --- /dev/null +++ b/neutronclient/tests/unit/bgp/test_cli20_peer.py @@ -0,0 +1,223 @@ +# Copyright 2016 Huawei Technologies India Pvt. Ltd. +# 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 sys + +from neutronclient.common import exceptions +from neutronclient.neutron.v2_0.bgp import peer as bgp_peer +from neutronclient.neutron.v2_0.bgp import speaker as bgp_speaker +from neutronclient.tests.unit import test_cli20 + + +class CLITestV20BGPPeerJSON(test_cli20.CLITestV20Base): + + non_admin_status_resources = ['bgp_peer'] + + def test_create_bgp_peer_with_mandatory_params(self): + # Create BGP peer with mandatory params. + resource = 'bgp_peer' + cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + peerip = '1.1.1.1' + remote_asnum = '1' + args = [name, + '--peer-ip', peerip, + '--remote-as', remote_asnum, ] + position_names = ['name', 'peer_ip', 'remote_as', + 'auth_type'] + position_values = [name, peerip, remote_asnum, 'none'] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values) + + def test_create_bgp_peer_with_all_params(self): + # Create BGP peer with all params. + resource = 'bgp_peer' + cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + peerip = '1.1.1.1' + remote_asnum = '65535' + authType = 'md5' + password = 'abc' + args = [name, + '--peer-ip', peerip, + '--remote-as', remote_asnum, + '--auth-type', authType, + '--password', password] + position_names = ['name', 'peer_ip', 'remote_as', + 'auth_type', 'password'] + position_values = [name, peerip, remote_asnum, authType, password] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values) + + def test_create_bgp_peer_with_invalid_min_remote_asnum(self): + # Create BGP peer with invalid minimum remote-asnum. + resource = 'bgp_peer' + cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + peerip = '1.1.1.1' + remote_asnum = '0' + args = [name, + '--peer-ip', peerip, + '--remote-as', remote_asnum, ] + position_names = ['name', 'peer_ip', 'remote_as', ] + position_values = [name, peerip, remote_asnum, ] + exc = self.assertRaises(exceptions.CommandError, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) + self.assertEqual('remote-as "0" should be an integer [%s:%s].' % + (bgp_speaker.MIN_AS_NUM, bgp_speaker.MAX_AS_NUM), + str(exc)) + + def test_create_bgp_peer_with_invalid_max_remote_asnum(self): + # Create BGP peer with invalid maximum remote-asnum. + resource = 'bgp_peer' + cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + peerip = '1.1.1.1' + remote_asnum = '65536' + args = [name, + '--peer-ip', peerip, + '--remote-as', remote_asnum, ] + position_names = ['name', 'peer_ip', 'remote_as', + 'auth_type', 'password'] + position_values = [name, peerip, remote_asnum, 'none', ''] + exc = self.assertRaises(exceptions.CommandError, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) + self.assertEqual('remote-as "65536" should be an integer [%s:%s].' % + (bgp_speaker.MIN_AS_NUM, bgp_speaker.MAX_AS_NUM), + str(exc)) + + def test_create_authenticated_bgp_peer_without_authtype(self): + # Create authenticated BGP peer without auth-type. + resource = 'bgp_peer' + cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + peerip = '1.1.1.1' + remote_asnum = '2048' + password = 'abc' + args = [name, + '--peer-ip', peerip, + '--remote-as', remote_asnum, + '--password', password] + position_names = ['name', 'peer_ip', 'remote_as', 'password'] + position_values = [name, peerip, remote_asnum, password] + exc = self.assertRaises(exceptions.CommandError, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) + self.assertEqual('Must provide auth-type if password is specified.', + str(exc)) + + def test_create_authenticated_bgp_peer_without_password(self): + # Create authenticated BGP peer without password. + resource = 'bgp_peer' + cmd = bgp_peer.CreatePeer(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + peerip = '1.1.1.1' + remote_asnum = '2048' + authType = 'md5' + args = [name, + '--peer-ip', peerip, + '--remote-as', remote_asnum, + '--auth-type', authType] + position_names = ['name', 'peer_ip', 'remote_as', 'auth-type'] + position_values = [name, peerip, remote_asnum, authType] + exc = self.assertRaises(exceptions.CommandError, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) + self.assertEqual('Must provide password if auth-type is specified.', + str(exc)) + + def test_update_bgp_peer(self): + # Update BGP peer: + # myid --advertise-tenant-networks True + # --advertise-floating-ip-host-routes False + resource = 'bgp_peer' + cmd = bgp_peer.UpdatePeer(test_cli20.MyApp(sys.stdout), + None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', 'new-name', + '--password', 'abc'], + {'name': 'new-name', 'password': 'abc'}) + + def test_update_bgp_peer_exception(self): + # Update BGP peer: myid. + resource = 'bgp_peer' + cmd = bgp_peer.UpdatePeer(test_cli20.MyApp(sys.stdout), + None) + self.assertRaises(exceptions.CommandError, + self._test_update_resource, + resource, cmd, 'myid', ['myid'], {}) + + def test_list_bgp_peer(self): + # List all BGP peers. + resources = "bgp_peers" + cmd = bgp_peer.ListPeers(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, True) + + # TODO(Vikram): Add test_list_bgp_peer_pagination + + def test_list_bgp_peer_sort(self): + # sorted list: bgp-peer-list --sort-key name --sort-key id + # --sort-key asc --sort-key desc + resources = "bgp_peers" + cmd = bgp_peer.ListPeers(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_bgp_peer_limit(self): + # size (1000) limited list: bgp-peer-list -P. + resources = "bgp_peers" + cmd = bgp_peer.ListPeers(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, page_size=1000) + + def test_show_bgp_peer(self): + # Show BGP peer: --fields id --fields name myid. + resource = 'bgp_peer' + cmd = bgp_peer.ShowPeer(test_cli20.MyApp(sys.stdout), + None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, args, + ['id', 'name']) + + def test_delete_bgp_peer(self): + # Delete BGP peer: bgp_peer_id. + resource = 'bgp_peer' + cmd = bgp_peer.DeletePeer(test_cli20.MyApp(sys.stdout), + None) + myid = 'myid' + args = [myid] + self._test_delete_resource(resource, cmd, myid, args) diff --git a/neutronclient/tests/unit/bgp/test_cli20_speaker.py b/neutronclient/tests/unit/bgp/test_cli20_speaker.py new file mode 100644 index 0000000..63ce5fc --- /dev/null +++ b/neutronclient/tests/unit/bgp/test_cli20_speaker.py @@ -0,0 +1,267 @@ +# Copyright 2016 Huawei Technologies India Pvt. Ltd. +# 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 sys + +from mox3 import mox + +from neutronclient.common import exceptions +from neutronclient.neutron.v2_0.bgp import speaker as bgp_speaker +from neutronclient.tests.unit import test_cli20 + + +class CLITestV20BGPSpeakerJSON(test_cli20.CLITestV20Base): + + non_admin_status_resources = ['bgp_speaker'] + + def test_create_bgp_speaker_with_minimal_options(self): + # Create BGP Speaker with mandatory params. + resource = 'bgp_speaker' + cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + local_asnum = '1' + args = [name, '--local-as', local_asnum, ] + position_names = ['name', 'local_as', 'ip_version'] + position_values = [name, local_asnum, 4] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values) + + def test_create_ipv4_bgp_speaker_with_all_params(self): + # Create BGP Speaker with all params. + resource = 'bgp_speaker' + cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + local_asnum = '1' + args = [name, + '--local-as', local_asnum, + '--ip-version', '4', + '--advertise-floating-ip-host-routes', 'True', + '--advertise-tenant-networks', 'True'] + position_names = ['name', 'local_as', 'ip_version', + 'advertise_floating_ip_host_routes', + 'advertise_tenant_networks'] + position_values = [name, local_asnum, 4, 'True', 'True'] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values) + + def test_create_ipv6_bgp_speaker_with_all_params(self): + # Create BGP Speaker with all params. + resource = 'bgp_speaker' + cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + local_asnum = '65535' + args = [name, + '--local-as', local_asnum, + '--ip-version', '6', + '--advertise-floating-ip-host-routes', 'True', + '--advertise-tenant-networks', 'True'] + position_names = ['name', 'local_as', 'ip_version', + 'advertise_floating_ip_host_routes', + 'advertise_tenant_networks'] + position_values = [name, local_asnum, 6, 'True', 'True'] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values) + + def test_create_bgp_speaker_with_invalid_min_local_asnum(self): + # Create BGP Speaker with invalid minimum local-asnum. + resource = 'bgp_speaker' + cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + local_asnum = '0' + args = [name, + '--local-as', local_asnum] + position_names = ['name', 'local_as'] + position_values = [name, local_asnum] + exc = self.assertRaises(exceptions.CommandError, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) + self.assertEqual('local-as "0" should be an integer [%s:%s].' % + (bgp_speaker.MIN_AS_NUM, bgp_speaker.MAX_AS_NUM), + str(exc)) + + def test_create_bgp_speaker_with_invalid_max_local_asnum(self): + # Create BGP Speaker with invalid maximum local-asnum. + resource = 'bgp_speaker' + cmd = bgp_speaker.CreateSpeaker(test_cli20.MyApp(sys.stdout), + None) + name = 'my-name' + my_id = 'my-id' + local_asnum = '65536' + args = [name, + '--local-as', local_asnum] + position_names = ['name', 'local_as', ] + position_values = [name, local_asnum, ] + exc = self.assertRaises(exceptions.CommandError, + self._test_create_resource, + resource, cmd, name, my_id, args, + position_names, position_values) + self.assertEqual('local-as "65536" should be an integer [%s:%s].' % + (bgp_speaker.MIN_AS_NUM, bgp_speaker.MAX_AS_NUM), + str(exc)) + + def test_update_bgp_speaker(self): + # Update BGP Speaker: + # myid --advertise-tenant-networks True + # --advertise-floating-ip-host-routes False + resource = 'bgp_speaker' + cmd = bgp_speaker.UpdateSpeaker(test_cli20.MyApp(sys.stdout), + None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', + '--name', 'new-name', + '--advertise-tenant-networks', 'True', + '--advertise-floating-ip-host-routes', + 'False'], + {'name': 'new-name', + 'advertise_tenant_networks': 'True', + 'advertise_floating_ip_host_routes': + 'False'}) + + def test_update_bgp_speaker_exception(self): + # Update BGP Speaker: myid. + resource = 'bgp_speaker' + cmd = bgp_speaker.UpdateSpeaker(test_cli20.MyApp(sys.stdout), + None) + self.assertRaises(exceptions.CommandError, + self._test_update_resource, + resource, cmd, 'myid', ['myid'], {}) + + def test_list_bgp_speaker(self): + # List all BGP Speakers. + resources = "bgp_speakers" + cmd = bgp_speaker.ListSpeakers(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, True) + + def test_list_bgp_speaker_pagination(self): + # List all BGP Speakers with pagination support. + cmd = bgp_speaker.ListSpeakers(test_cli20.MyApp(sys.stdout), + None) + self.mox.StubOutWithMock(bgp_speaker.ListSpeakers, + "extend_list") + bgp_speaker.ListSpeakers.extend_list(mox.IsA(list), + mox.IgnoreArg()) + self._test_list_resources_with_pagination("bgp_speakers", + cmd) + self.mox.VerifyAll() + self.mox.UnsetStubs() + + def test_list_bgp_speaker_sort(self): + # sorted list: bgp-speaker-list --sort-key name --sort-key id + # --sort-key asc --sort-key desc + resources = "bgp_speakers" + cmd = bgp_speaker.ListSpeakers(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_bgp_speaker_limit(self): + # size (1000) limited list: bgp-speaker-list -P. + resources = "bgp_speakers" + cmd = bgp_speaker.ListSpeakers(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, page_size=1000) + + def test_show_bgp_speaker(self): + # Show BGP Speaker: --fields id --fields name myid. + resource = 'bgp_speaker' + cmd = bgp_speaker.ShowSpeaker(test_cli20.MyApp(sys.stdout), + None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, args, + ['id', 'name']) + + def test_delete_bgp_speaker(self): + # Delete BGP Speaker: bgp_speaker_id. + resource = 'bgp_speaker' + cmd = bgp_speaker.DeleteSpeaker(test_cli20.MyApp(sys.stdout), + None) + myid = 'myid' + args = [myid] + self._test_delete_resource(resource, cmd, myid, args) + + def _test_add_remove_peer(self, action, cmd, args): + """Add or Remove BGP Peer to/from a BGP Speaker.""" + resource = 'bgp_speaker' + subcmd = '%s_bgp_peer' % action + body = {'bgp_peer_id': 'peerid'} + if action == 'add': + retval = {'bgp_peer': 'peerid'} + else: + retval = None + self._test_update_resource_action(resource, cmd, 'myid', + subcmd, args, body, retval) + + def test_add_peer_to_bgp_speaker(self): + # Add peer to BGP speaker: myid peer_id=peerid + cmd = bgp_speaker.AddPeerToSpeaker(test_cli20.MyApp(sys.stdout), + None) + args = ['myid', 'peerid'] + self._test_add_remove_peer('add', cmd, args) + + def test_remove_peer_from_bgp_speaker(self): + # Remove peer from BGP speaker: myid peer_id=peerid + cmd = bgp_speaker.RemovePeerFromSpeaker(test_cli20.MyApp(sys.stdout), + None) + args = ['myid', 'peerid'] + self._test_add_remove_peer('remove', cmd, args) + + def _test_add_remove_network(self, action, cmd, args): + # Add or Remove network to/from a BGP Speaker. + resource = 'bgp_speaker' + subcmd = '%s_gateway_network' % action + body = {'network_id': 'netid'} + if action == 'add': + retval = {'network': 'netid'} + else: + retval = None + self._test_update_resource_action(resource, cmd, 'myid', + subcmd, args, body, retval) + + def test_add_network_to_bgp_speaker(self): + # Add peer to BGP speaker: myid network_id=netid + cmd = bgp_speaker.AddNetworkToSpeaker(test_cli20.MyApp(sys.stdout), + None) + args = ['myid', 'netid'] + self._test_add_remove_network('add', cmd, args) + + def test_remove_network_from_bgp_speaker(self): + # Remove network from BGP speaker: myid network_id=netid + cmd = bgp_speaker.RemoveNetworkFromSpeaker( + test_cli20.MyApp(sys.stdout), None) + args = ['myid', 'netid'] + self._test_add_remove_network('remove', cmd, args) + + def test_list_routes_advertised_by_a_bgp_speaker(self): + # Retrieve advertised route list + resources = 'advertised_routes' + cmd = bgp_speaker.ListRoutesAdvertisedBySpeaker( + test_cli20.MyApp(sys.stdout), None) + bs_id = 'bgp_speaker_id1' + path = ((self.client.bgp_speaker_path + '/get_advertised_routes') % + bs_id) + self._test_list_resources(resources, cmd, base_args=[bs_id], + path=path) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index df0c46c..cfd6264 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -410,6 +410,14 @@ class Client(ClientBase): flavor_profile_binding_path = flavor_path + service_profile_path availability_zones_path = "/availability_zones" auto_allocated_topology_path = "/auto-allocated-topology/%s" + BGP_DRINSTANCES = "/bgp-drinstances" + BGP_DRINSTANCE = "/bgp-drinstance/%s" + BGP_DRAGENTS = "/bgp-dragents" + BGP_DRAGENT = "/bgp-dragents/%s" + bgp_speakers_path = "/bgp-speakers" + bgp_speaker_path = "/bgp-speakers/%s" + bgp_peers_path = "/bgp-peers" + bgp_peer_path = "/bgp-peers/%s" # API has no way to report plurals, so we have to hard code them EXTED_PLURALS = {'routers': 'router', @@ -447,6 +455,8 @@ class Client(ClientBase): 'bandwidth_limit_rules': 'bandwidth_limit_rule', 'rule_types': 'rule_type', 'flavors': 'flavor', + 'bgp_speakers': 'bgp_speaker', + 'bgp_peers': 'bgp_peer', } @APIParamsCall @@ -1346,6 +1356,30 @@ class Client(ClientBase): return self.post((self.agent_path + self.L3_ROUTERS) % l3_agent, body=body) + @APIParamsCall + def list_dragents_hosting_bgp_speaker(self, bgp_speaker, **_params): + """Fetches a list of Dynamic Routing agents hosting a BGP speaker.""" + return self.get((self.bgp_speaker_path + self.BGP_DRAGENTS) + % bgp_speaker, params=_params) + + @APIParamsCall + def add_bgp_speaker_to_dragent(self, bgp_dragent, body): + """Adds a BGP speaker to Dynamic Routing agent.""" + return self.post((self.agent_path + self.BGP_DRINSTANCES) + % bgp_dragent, body=body) + + @APIParamsCall + def remove_bgp_speaker_from_dragent(self, bgp_dragent, bgpspeaker_id): + """Removes a BGP speaker from Dynamic Routing agent.""" + return self.delete((self.agent_path + self.BGP_DRINSTANCES + "/%s") + % (bgp_dragent, bgpspeaker_id)) + + @APIParamsCall + def list_bgp_speaker_on_dragent(self, bgp_dragent, **_params): + """Fetches a list of BGP speakers hosted by Dynamic Routing agent.""" + return self.get((self.agent_path + self.BGP_DRINSTANCES) + % bgp_dragent, params=_params) + @APIParamsCall def list_firewall_rules(self, retrieve_all=True, **_params): """Fetches a list of all firewall rules for a tenant.""" @@ -1701,6 +1735,89 @@ class Client(ClientBase): self.auto_allocated_topology_path % tenant_id, params=_params) + @APIParamsCall + def list_bgp_speakers(self, retrieve_all=True, **_params): + """Fetches a list of all BGP speakers for a tenant.""" + return self.list('bgp_speakers', self.bgp_speakers_path, retrieve_all, + **_params) + + @APIParamsCall + def show_bgp_speaker(self, bgp_speaker_id, **_params): + """Fetches information of a certain BGP speaker.""" + return self.get(self.bgp_speaker_path % (bgp_speaker_id), + params=_params) + + @APIParamsCall + def create_bgp_speaker(self, body=None): + """Creates a new BGP speaker.""" + return self.post(self.bgp_speakers_path, body=body) + + @APIParamsCall + def update_bgp_speaker(self, bgp_speaker_id, body=None): + """Update a BGP speaker.""" + return self.put(self.bgp_speaker_path % bgp_speaker_id, body=body) + + @APIParamsCall + def delete_bgp_speaker(self, speaker_id): + """Deletes the specified BGP speaker.""" + return self.delete(self.bgp_speaker_path % (speaker_id)) + + @APIParamsCall + def add_peer_to_bgp_speaker(self, speaker_id, body=None): + """Adds a peer to BGP speaker.""" + return self.put((self.bgp_speaker_path % speaker_id) + + "/add_bgp_peer", body=body) + + @APIParamsCall + def remove_peer_from_bgp_speaker(self, speaker_id, body=None): + """Removes a peer from BGP speaker.""" + return self.put((self.bgp_speaker_path % speaker_id) + + "/remove_bgp_peer", body=body) + + @APIParamsCall + def add_network_to_bgp_speaker(self, speaker_id, body=None): + """Adds a network to BGP speaker.""" + return self.put((self.bgp_speaker_path % speaker_id) + + "/add_gateway_network", body=body) + + @APIParamsCall + def remove_network_from_bgp_speaker(self, speaker_id, body=None): + """Removes a network from BGP speaker.""" + return self.put((self.bgp_speaker_path % speaker_id) + + "/remove_gateway_network", body=body) + + @APIParamsCall + def list_route_advertised_from_bgp_speaker(self, speaker_id, **_params): + """Fetches a list of all routes advertised by BGP speaker.""" + return self.get((self.bgp_speaker_path % speaker_id) + + "/get_advertised_routes", params=_params) + + @APIParamsCall + def list_bgp_peers(self, **_params): + """Fetches a list of all BGP peers.""" + return self.get(self.bgp_peers_path, params=_params) + + @APIParamsCall + def show_bgp_peer(self, peer_id, **_params): + """Fetches information of a certain BGP peer.""" + return self.get(self.bgp_peer_path % peer_id, + params=_params) + + @APIParamsCall + def create_bgp_peer(self, body=None): + """Create a new BGP peer.""" + return self.post(self.bgp_peers_path, body=body) + + @APIParamsCall + def update_bgp_peer(self, bgp_peer_id, body=None): + """Update a BGP peer.""" + return self.put(self.bgp_peer_path % bgp_peer_id, body=body) + + @APIParamsCall + def delete_bgp_peer(self, peer_id): + """Deletes the specified BGP peer.""" + return self.delete(self.bgp_peer_path % peer_id) + def __init__(self, **kwargs): """Initialize a new client for the Neutron v2.0 API.""" super(Client, self).__init__(**kwargs) diff --git a/releasenotes/notes/bgp-dynamic-routing-b97a1c81d3007049.yaml b/releasenotes/notes/bgp-dynamic-routing-b97a1c81d3007049.yaml new file mode 100644 index 0000000..2fa2e8e --- /dev/null +++ b/releasenotes/notes/bgp-dynamic-routing-b97a1c81d3007049.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + CLI support for the BGP dynamic routing functionality will help + advertising neutron fixed-ips and dvr host routes via BGP. \ No newline at end of file From 65118c09eb08e760590939e57d3b1a485c567f91 Mon Sep 17 00:00:00 2001 From: Hirofumi Ichihara Date: Wed, 24 Feb 2016 19:28:28 +0900 Subject: [PATCH 42/57] Add wrapper classes for return-request-id-to-caller Added wrapper classes which are inherited from base data types tuple, dict and str. Each of these wrapper classes contain a 'request_ids' attribute which is populated with a 'x-openstack-request-id' received in a header from a response body. This change is required to return 'request_id' from client to log request_id mappings of cross projects[1]. [1]: http://specs.openstack.org/openstack/openstack-specs/specs/return-request-id.html Change-Id: I55fcba61c4efb308f575e95e154aba23e5dd5245 Implements: blueprint return-request-id-to-caller --- doc/source/usage/library.rst | 10 + neutronclient/common/exceptions.py | 9 + neutronclient/tests/unit/test_cli20.py | 204 ++++++++++++++++-- neutronclient/v2_0/client.py | 124 ++++++++++- ...request-id-to-caller-15b1d23a4ddc27a3.yaml | 3 + 5 files changed, 327 insertions(+), 23 deletions(-) create mode 100644 releasenotes/notes/return-request-id-to-caller-15b1d23a4ddc27a3.yaml diff --git a/doc/source/usage/library.rst b/doc/source/usage/library.rst index dff0696..8b301e3 100644 --- a/doc/source/usage/library.rst +++ b/doc/source/usage/library.rst @@ -59,3 +59,13 @@ and a service endpoint URL directly. >>> from neutronclient.v2_0 import client >>> neutron = client.Client(endpoint_url='http://192.168.206.130:9696/', ... token='d3f9226f27774f338019aa2611112ef6') + +You can get ``X-Openstack-Request-Id`` as ``request_ids`` from the result. + +.. code-block:: python + + >>> network = {'name': 'mynetwork', 'admin_state_up': True} + >>> neutron.create_network({'network':network}) + >>> networks = neutron.list_networks(name='mynetwork') + >>> print networks.request_ids + ['req-978a0160-7ab0-44f0-8a93-08e9a4e785fa'] diff --git a/neutronclient/common/exceptions.py b/neutronclient/common/exceptions.py index 95f54f7..6aff6d6 100644 --- a/neutronclient/common/exceptions.py +++ b/neutronclient/common/exceptions.py @@ -60,10 +60,19 @@ class NeutronClientException(NeutronException): """ status_code = 0 + req_ids_msg = _("Neutron server returns request_ids: %s") + request_ids = [] def __init__(self, message=None, **kwargs): + self.request_ids = kwargs.get('request_ids') if 'status_code' in kwargs: self.status_code = kwargs['status_code'] + if self.request_ids: + req_ids_msg = self.req_ids_msg % self.request_ids + if message: + message += '\n' + req_ids_msg + else: + message = req_ids_msg super(NeutronClientException, self).__init__(message, **kwargs) diff --git a/neutronclient/tests/unit/test_cli20.py b/neutronclient/tests/unit/test_cli20.py index e3a1014..5407e7b 100644 --- a/neutronclient/tests/unit/test_cli20.py +++ b/neutronclient/tests/unit/test_cli20.py @@ -37,6 +37,7 @@ API_VERSION = "2.0" FORMAT = 'json' TOKEN = 'testtoken' ENDURL = 'localurl' +REQUEST_ID = 'test_request_id' @contextlib.contextmanager @@ -65,7 +66,7 @@ class FakeStdout(object): return result -class MyResp(object): +class MyResp(requests.Response): def __init__(self, status_code, headers=None, reason=None): self.status_code = status_code self.headers = headers or {} @@ -648,41 +649,46 @@ class ClientV2TestJson(CLITestV20Base): self.client.httpclient.auth_token = encodeutils.safe_encode( unicode_text) expected_auth_token = encodeutils.safe_encode(unicode_text) + resp_headers = {'x-openstack-request-id': REQUEST_ID} self.client.httpclient.request( end_url(expected_action, query=expect_query, format=self.format), 'PUT', body=expect_body, headers=mox.ContainsKeyValue( 'X-Auth-Token', - expected_auth_token)).AndReturn((MyResp(200), expect_body)) + expected_auth_token)).AndReturn((MyResp(200, resp_headers), + expect_body)) self.mox.ReplayAll() - res_body = self.client.do_request('PUT', action, body=body, - params=params) + result = self.client.do_request('PUT', action, body=body, + params=params) self.mox.VerifyAll() self.mox.UnsetStubs() # test response with unicode - self.assertEqual(body, res_body) + self.assertEqual(body, result) def test_do_request_error_without_response_body(self): self.mox.StubOutWithMock(self.client.httpclient, "request") params = {'test': 'value'} expect_query = six.moves.urllib.parse.urlencode(params) self.client.httpclient.auth_token = 'token' + resp_headers = {'x-openstack-request-id': REQUEST_ID} self.client.httpclient.request( MyUrlComparator(end_url( '/test', query=expect_query, format=self.format), self.client), 'PUT', body='', headers=mox.ContainsKeyValue('X-Auth-Token', 'token') - ).AndReturn((MyResp(400, reason='An error'), '')) + ).AndReturn((MyResp(400, headers=resp_headers, reason='An error'), '')) self.mox.ReplayAll() error = self.assertRaises(exceptions.NeutronClientException, self.client.do_request, 'PUT', '/test', body='', params=params) - self.assertEqual("An error", str(error)) + expected_error = "An error\nNeutron server returns " \ + "request_ids: %s" % [REQUEST_ID] + self.assertEqual(expected_error, str(error)) self.mox.VerifyAll() self.mox.UnsetStubs() @@ -697,21 +703,126 @@ class ClientV2TestJson(CLITestV20Base): else: self.fail('Expected exception NOT raised') + def test_do_request_request_ids(self): + self.mox.StubOutWithMock(self.client.httpclient, "request") + params = {'test': 'value'} + expect_query = six.moves.urllib.parse.urlencode(params) + self.client.httpclient.auth_token = 'token' + body = params + expect_body = self.client.serialize(body) + resp_headers = {'x-openstack-request-id': REQUEST_ID} + self.client.httpclient.request( + MyUrlComparator(end_url( + '/test', query=expect_query, + format=self.format), self.client), + 'PUT', body=expect_body, + headers=mox.ContainsKeyValue('X-Auth-Token', 'token') + ).AndReturn((MyResp(200, resp_headers), expect_body)) + + self.mox.ReplayAll() + result = self.client.do_request('PUT', '/test', body=body, + params=params) + self.mox.VerifyAll() + self.mox.UnsetStubs() + + self.assertEqual(body, result) + self.assertEqual([REQUEST_ID], result.request_ids) + + def test_list_request_ids_with_retrieve_all_true(self): + self.mox.StubOutWithMock(self.client.httpclient, "request") + + path = '/test' + resources = 'tests' + fake_query = "marker=myid2&limit=2" + reses1 = {resources: [{'id': 'myid1', }, + {'id': 'myid2', }], + '%s_links' % resources: [{'href': end_url(path, fake_query), + 'rel': 'next'}]} + reses2 = {resources: [{'id': 'myid3', }, + {'id': 'myid4', }]} + resstr1 = self.client.serialize(reses1) + resstr2 = self.client.serialize(reses2) + resp_headers = {'x-openstack-request-id': REQUEST_ID} + self.client.httpclient.request( + end_url(path, "", format=self.format), 'GET', + body=None, + headers=mox.ContainsKeyValue( + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(200, resp_headers), + resstr1)) + self.client.httpclient.request( + MyUrlComparator(end_url(path, fake_query, format=self.format), + self.client), 'GET', + body=None, + headers=mox.ContainsKeyValue( + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(200, resp_headers), + resstr2)) + self.mox.ReplayAll() + result = self.client.list(resources, path) + + self.mox.VerifyAll() + self.mox.UnsetStubs() + + self.assertEqual([REQUEST_ID, REQUEST_ID], result.request_ids) + + def test_list_request_ids_with_retrieve_all_false(self): + self.mox.StubOutWithMock(self.client.httpclient, "request") + + path = '/test' + resources = 'tests' + fake_query = "marker=myid2&limit=2" + reses1 = {resources: [{'id': 'myid1', }, + {'id': 'myid2', }], + '%s_links' % resources: [{'href': end_url(path, fake_query), + 'rel': 'next'}]} + reses2 = {resources: [{'id': 'myid3', }, + {'id': 'myid4', }]} + resstr1 = self.client.serialize(reses1) + resstr2 = self.client.serialize(reses2) + resp_headers = {'x-openstack-request-id': REQUEST_ID} + self.client.httpclient.request( + end_url(path, "", format=self.format), 'GET', + body=None, + headers=mox.ContainsKeyValue( + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(200, resp_headers), + resstr1)) + self.client.httpclient.request( + MyUrlComparator(end_url(path, fake_query, format=self.format), + self.client), 'GET', + body=None, + headers=mox.ContainsKeyValue( + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(200, resp_headers), + resstr2)) + self.mox.ReplayAll() + result = self.client.list(resources, path, retrieve_all=False) + next(result) + self.assertEqual([REQUEST_ID], result.request_ids) + next(result) + self.assertEqual([REQUEST_ID, REQUEST_ID], result.request_ids) + self.mox.VerifyAll() + self.mox.UnsetStubs() + class CLITestV20ExceptionHandler(CLITestV20Base): def _test_exception_handler_v20( self, expected_exception, status_code, expected_msg, error_type=None, error_msg=None, error_detail=None, - error_content=None): + request_id=None, error_content=None): + + resp = MyResp(status_code, {'x-openstack-request-id': request_id}) + if request_id is not None: + expected_msg += "\nNeutron server returns " \ + "request_ids: %s" % [request_id] if error_content is None: error_content = {'NeutronError': {'type': error_type, 'message': error_msg, 'detail': error_detail}} + expected_content = self.client._convert_into_with_meta(error_content, + resp) e = self.assertRaises(expected_exception, client.exception_handler_v20, - status_code, error_content) + status_code, expected_content) self.assertEqual(status_code, e.status_code) self.assertEqual(expected_exception.__name__, e.__class__.__name__) @@ -728,7 +839,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base): 'fake-network-uuid. The IP address fake-ip is in use.') self._test_exception_handler_v20( exceptions.IpAddressInUseClient, 409, err_msg, - 'IpAddressInUse', err_msg, '') + 'IpAddressInUse', err_msg, '', REQUEST_ID) def test_exception_handler_v20_neutron_known_error(self): known_error_map = [ @@ -754,7 +865,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base): self._test_exception_handler_v20( client_exc, status_code, error_msg + '\n' + error_detail, - server_exc, error_msg, error_detail) + server_exc, error_msg, error_detail, REQUEST_ID) def test_exception_handler_v20_neutron_known_error_without_detail(self): error_msg = 'Network not found' @@ -762,7 +873,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base): self._test_exception_handler_v20( exceptions.NetworkNotFoundClient, 404, error_msg, - 'NetworkNotFound', error_msg, error_detail) + 'NetworkNotFound', error_msg, error_detail, REQUEST_ID) def test_exception_handler_v20_unknown_error_to_per_code_exception(self): for status_code, client_exc in exceptions.HTTP_EXCEPTION_MAP.items(): @@ -771,7 +882,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base): self._test_exception_handler_v20( client_exc, status_code, error_msg + '\n' + error_detail, - 'UnknownError', error_msg, error_detail) + 'UnknownError', error_msg, error_detail, [REQUEST_ID]) def test_exception_handler_v20_neutron_unknown_status_code(self): error_msg = 'Unknown error' @@ -779,7 +890,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base): self._test_exception_handler_v20( exceptions.NeutronClientException, 501, error_msg + '\n' + error_detail, - 'UnknownError', error_msg, error_detail) + 'UnknownError', error_msg, error_detail, REQUEST_ID) def test_exception_handler_v20_bad_neutron_error(self): for status_code, client_exc in exceptions.HTTP_EXCEPTION_MAP.items(): @@ -787,7 +898,8 @@ class CLITestV20ExceptionHandler(CLITestV20Base): self._test_exception_handler_v20( client_exc, status_code, expected_msg="{'unknown_key': 'UNKNOWN'}", - error_content=error_content) + error_content=error_content, + request_id=REQUEST_ID) def test_exception_handler_v20_error_dict_contains_message(self): error_content = {'message': 'This is an error message'} @@ -795,7 +907,8 @@ class CLITestV20ExceptionHandler(CLITestV20Base): self._test_exception_handler_v20( client_exc, status_code, expected_msg='This is an error message', - error_content=error_content) + error_content=error_content, + request_id=REQUEST_ID) def test_exception_handler_v20_error_dict_not_contain_message(self): # 599 is not contained in HTTP_EXCEPTION_MAP. @@ -804,6 +917,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base): self._test_exception_handler_v20( exceptions.NeutronClientException, 599, expected_msg=expected_msg, + request_id=None, error_content=error_content) def test_exception_handler_v20_default_fallback(self): @@ -813,6 +927,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base): self._test_exception_handler_v20( exceptions.NeutronClientException, 599, expected_msg=expected_msg, + request_id=None, error_content=error_content) def test_exception_status(self): @@ -848,3 +963,60 @@ class CLITestV20ExceptionHandler(CLITestV20Base): self.assertIsNotNone(error.status_code) self.mox.VerifyAll() self.mox.UnsetStubs() + + +class DictWithMetaTest(base.BaseTestCase): + + def test_dict_with_meta(self): + body = {'test': 'value'} + resp = MyResp(200, {'x-openstack-request-id': REQUEST_ID}) + obj = client._DictWithMeta(body, resp) + self.assertEqual(body, obj) + + # Check request_ids attribute is added to obj + self.assertTrue(hasattr(obj, 'request_ids')) + self.assertEqual([REQUEST_ID], obj.request_ids) + + +class TupleWithMetaTest(base.BaseTestCase): + + def test_tuple_with_meta(self): + body = ('test', 'value') + resp = MyResp(200, {'x-openstack-request-id': REQUEST_ID}) + obj = client._TupleWithMeta(body, resp) + self.assertEqual(body, obj) + + # Check request_ids attribute is added to obj + self.assertTrue(hasattr(obj, 'request_ids')) + self.assertEqual([REQUEST_ID], obj.request_ids) + + +class StrWithMetaTest(base.BaseTestCase): + + def test_str_with_meta(self): + body = "test_string" + resp = MyResp(200, {'x-openstack-request-id': REQUEST_ID}) + obj = client._StrWithMeta(body, resp) + self.assertEqual(body, obj) + + # Check request_ids attribute is added to obj + self.assertTrue(hasattr(obj, 'request_ids')) + self.assertEqual([REQUEST_ID], obj.request_ids) + + +class GeneratorWithMetaTest(base.BaseTestCase): + + body = {'test': 'value'} + resp = MyResp(200, {'x-openstack-request-id': REQUEST_ID}) + + def _pagination(self, collection, path, **params): + obj = client._DictWithMeta(self.body, self.resp) + yield obj + + def test_generator(self): + obj = client._GeneratorWithMeta(self._pagination, 'test_collection', + 'test_path', test_args='test_args') + self.assertEqual(self.body, next(obj)) + + self.assertTrue(hasattr(obj, 'request_ids')) + self.assertEqual([REQUEST_ID], obj.request_ids) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index 08a8503..9fd2c6a 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -22,6 +22,7 @@ import time import requests import six.moves.urllib.parse as urlparse +from six import string_types from neutronclient._i18n import _ from neutronclient import client @@ -44,6 +45,7 @@ def exception_handler_v20(status_code, error_content): :param error_content: deserialized body of error response """ error_dict = None + request_ids = error_content.request_ids if isinstance(error_content, dict): error_dict = error_content.get('NeutronError') # Find real error type @@ -78,7 +80,8 @@ def exception_handler_v20(status_code, error_content): client_exc = exceptions.NeutronClientException raise client_exc(message=error_message, - status_code=status_code) + status_code=status_code, + request_ids=request_ids) class APIParamsCall(object): @@ -97,6 +100,99 @@ class APIParamsCall(object): return with_params +class _RequestIdMixin(object): + """Wrapper class to expose x-openstack-request-id to the caller.""" + def _request_ids_setup(self): + self._request_ids = [] + + @property + def request_ids(self): + return self._request_ids + + def _append_request_ids(self, resp): + """Add request_ids as an attribute to the object + + :param resp: Response object or list of Response objects + """ + if isinstance(resp, list): + # Add list of request_ids if response is of type list. + for resp_obj in resp: + self._append_request_id(resp_obj) + elif resp is not None: + # Add request_ids if response contains single object. + self._append_request_id(resp) + + def _append_request_id(self, resp): + if isinstance(resp, requests.Response): + # Extract 'x-openstack-request-id' from headers if + # response is a Response object. + request_id = resp.headers.get('x-openstack-request-id') + else: + # If resp is of type string. + request_id = resp + if request_id: + self._request_ids.append(request_id) + + +class _DictWithMeta(dict, _RequestIdMixin): + def __init__(self, values, resp): + super(_DictWithMeta, self).__init__(values) + self._request_ids_setup() + self._append_request_ids(resp) + + +class _TupleWithMeta(tuple, _RequestIdMixin): + def __new__(cls, values, resp): + return super(_TupleWithMeta, cls).__new__(cls, values) + + def __init__(self, values, resp): + self._request_ids_setup() + self._append_request_ids(resp) + + +class _StrWithMeta(str, _RequestIdMixin): + def __new__(cls, value, resp): + return super(_StrWithMeta, cls).__new__(cls, value) + + def __init__(self, values, resp): + self._request_ids_setup() + self._append_request_ids(resp) + + +class _GeneratorWithMeta(_RequestIdMixin): + def __init__(self, paginate_func, collection, path, **params): + self.paginate_func = paginate_func + self.collection = collection + self.path = path + self.params = params + self.generator = None + self._request_ids_setup() + + def _paginate(self): + for r in self.paginate_func( + self.collection, self.path, **self.params): + yield r, r.request_ids + + def __iter__(self): + return self + + # Python 3 compatibility + def __next__(self): + return self.next() + + def next(self): + if not self.generator: + self.generator = self._paginate() + + try: + obj, req_id = next(self.generator) + self._append_request_ids(req_id) + except StopIteration: + raise StopIteration() + + return obj + + class ClientBase(object): """Client for the OpenStack Neutron v2.0 API. @@ -162,7 +258,7 @@ class ClientBase(object): self.action_prefix = "/v%s" % (self.version) self.retry_interval = 1 - def _handle_fault_response(self, status_code, response_body): + def _handle_fault_response(self, status_code, response_body, resp): # Create exception with HTTP status code and message _logger.debug("Error message: %s", response_body) # Add deserialized error message to exception arguments @@ -172,8 +268,9 @@ class ClientBase(object): # If unable to deserialized body it is probably not a # Neutron error des_error_body = {'message': response_body} + error_body = self._convert_into_with_meta(des_error_body, resp) # Raise the appropriate exception - exception_handler_v20(status_code, des_error_body) + exception_handler_v20(status_code, error_body) def do_request(self, method, action, body=None, headers=None, params=None): # Add format and tenant_id @@ -193,11 +290,12 @@ class ClientBase(object): requests.codes.created, requests.codes.accepted, requests.codes.no_content): - return self.deserialize(replybody, status_code) + data = self.deserialize(replybody, status_code) + return self._convert_into_with_meta(data, resp) else: if not replybody: replybody = resp.reason - self._handle_fault_response(status_code, replybody) + self._handle_fault_response(status_code, replybody, resp) def get_auth_info(self): return self.httpclient.get_auth_info() @@ -271,11 +369,14 @@ class ClientBase(object): def list(self, collection, path, retrieve_all=True, **params): if retrieve_all: res = [] + request_ids = [] for r in self._pagination(collection, path, **params): res.extend(r[collection]) - return {collection: res} + request_ids.extend(r.request_ids) + return _DictWithMeta({collection: res}, request_ids) else: - return self._pagination(collection, path, **params) + return _GeneratorWithMeta(self._pagination, collection, + path, **params) def _pagination(self, collection, path, **params): if params.get('page_reverse', False): @@ -297,6 +398,15 @@ class ClientBase(object): except KeyError: break + def _convert_into_with_meta(self, item, resp): + if item: + if isinstance(item, dict): + return _DictWithMeta(item, resp) + elif isinstance(item, string_types): + return _StrWithMeta(item, resp) + else: + return _TupleWithMeta((), resp) + class Client(ClientBase): diff --git a/releasenotes/notes/return-request-id-to-caller-15b1d23a4ddc27a3.yaml b/releasenotes/notes/return-request-id-to-caller-15b1d23a4ddc27a3.yaml new file mode 100644 index 0000000..da7c5f8 --- /dev/null +++ b/releasenotes/notes/return-request-id-to-caller-15b1d23a4ddc27a3.yaml @@ -0,0 +1,3 @@ +--- +features: + - Neutron client returns 'x-openstack-request-id'. From 585a4ffbfa85cf27293431086056e67706fedc48 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 4 Feb 2016 16:02:18 -0600 Subject: [PATCH 43/57] Devref Update: Transition to OpenStack Client Update the "Transition to OpenStack Client" devref with information about the new OSC command specs process. This process can be used to propose new networking commands while deferring their implementation. This process may help speed the overall transition to OSC. This update also includes information about the OSC neutron support etherpad which provides detailed status for the transition. And finally, updates were made to the overall transition plan based on discussions at the midcycle. Change-Id: I46bc066d2169a1e20af13baac7131ffa2eedd7c8 Related-Bug: #1521291 --- doc/source/devref/transition_to_osc.rst | 158 ++++++++++++++++-------- 1 file changed, 104 insertions(+), 54 deletions(-) diff --git a/doc/source/devref/transition_to_osc.rst b/doc/source/devref/transition_to_osc.rst index 9d4c87a..0ac2c90 100644 --- a/doc/source/devref/transition_to_osc.rst +++ b/doc/source/devref/transition_to_osc.rst @@ -26,13 +26,13 @@ Transition to OpenStack Client This document details the transition roadmap for moving the neutron client's OpenStack Networking API support, both the Python library and the ``neutron`` command-line interface (CLI), to the -`OpenStack client (OSC) `_ +`OpenStack Client (OSC) `_ and the `OpenStack Python SDK `_. This transition is being guided by the `Deprecate individual CLIs in favour of OSC `_ -OpenStack spec. See the `Neutron RFE `_ and -`OSC neutron-client blueprint `_ -for the overall progress of this transition. +OpenStack spec. See the `Neutron RFE `_, +`OSC neutron support etherpad `_ and +details below for the overall progress of this transition. Overview -------- @@ -42,17 +42,15 @@ deprecated and then eventually removed. The ``neutron`` CLI will be replaced by OSC's networking support available via the ``openstack`` CLI. This is similar to the deprecation and removal process for the `keystone client's `_ -``keystone`` CLI. - -The neutron client's Python library won't be deprecated. It will be available -along side the networking support provided by the OpenStack Python SDK. However, -the OpenStack Python SDK will be used to implement OSC's networking support. +``keystone`` CLI. The neutron client's Python library won't be deprecated. +It will be available along side the networking support provided by the +OpenStack Python SDK. Users of the neutron client's command extensions will need to transition to the `OSC plugin system `_ before the ``neutron`` CLI is removed. Such users will maintain their OSC plugin -within their own project and will be responsible for deprecating and removing -their ``neutron`` CLI extension. +commands within their own project and will be responsible for deprecating and +removing their ``neutron`` CLI extension. Transition Steps ---------------- @@ -92,13 +90,12 @@ Transition Steps * `Security Group Rule CRUD `_ -6. **In Progress:** OSC enhances its networking support under the - `neutron-client `_ - OSC spec. At this point and when applicable, enhancements to the ``neutron`` +6. **In Progress:** OSC continues enhancing its networking support. + At this point and when applicable, enhancements to the ``neutron`` CLI must also be made to the ``openstack`` CLI and the OpenStack Python SDK. Enhancements to the networking support in the OpenStack Python SDK will be - handled via bugs. Neutron stadium users of the neutron client's command - extensions should start their transition to the OSC plugin system. + handled via bugs. Users of the neutron client's command extensions should + start their transition to the OSC plugin system. See the developer guide section below for more information on this step. 7. **Not Started:** Deprecate the ``neutron`` CLI once the criteria below have @@ -112,64 +109,116 @@ Transition Steps equivalent to the ``neutron`` CLI and it contains sufficient functional and unit test coverage. - * Neutron core and advanced services projects, Neutron documentation and - `DevStack `_ use ``openstack`` - CLI instead of ``neutron`` CLI. + * `Neutron Stadium `_ + projects, Neutron documentation and `DevStack `_ + use ``openstack`` CLI instead of ``neutron`` CLI. - * Most neutron stadium users of the neutron client's command extensions have - transitioned to the OSC plugin system and use the ``openstack`` CLI instead - of the ``neutron`` CLI. + * Most users of the neutron client's command extensions have transitioned + to the OSC plugin system and use the ``openstack`` CLI instead of the + ``neutron`` CLI. 8. **Not Started:** Remove the ``neutron`` CLI after two deprecation cycles. Developer Guide --------------- -The ``neutron`` CLI version 3.1.1, without extensions, supports over 200 -commands while the ``openstack`` CLI version 2.0.1 supports about 20 -networking commands. Of the 20 commands, most do not have all of the options +The ``neutron`` CLI version 4.x, without extensions, supports over 200 +commands while the ``openstack`` CLI version 2.1.0 supports about 30 +networking commands. Of the 30 commands, many do not have all of the options or arguments of their ``neutron`` CLI equivalent. With this large functional -gap, one critical question for developers during this transition is "Which -CLI do I change?" The answer depends on the state of a command and the -state of the overall transition. Details are outlined in the table -below. Early stages of the transition will require dual maintenance. -Eventually, dual maintenance will be reduced to critical bug fixes only -with feature requests only being made to the ``openstack`` CLI. +gap, a couple critical questions for developers during this transition are "Which +CLI do I change?" and "Where does my CLI belong?" The answer depends on the +state of a command and the state of the overall transition. Details are +outlined in the tables below. Early stages of the transition will require dual +maintenance. Eventually, dual maintenance will be reduced to critical bug fixes +only with feature requests only being made to the ``openstack`` CLI. -+----------------------+------------------------+----------------------------------------------+ -| neutron Command | openstack Command | CLI to Change | -+======================+========================+==============================================+ -| Exists | Doesn't Exist | neutron | -+----------------------+------------------------+----------------------------------------------+ -| Exists | In Progress | neutron and update related OSC bug | -+----------------------+------------------------+----------------------------------------------+ -| Exists | Exists | neutron and openstack | -+----------------------+------------------------+----------------------------------------------+ -| Doesn't Exist | Doesn't Exist | neutron and openstack | -+----------------------+------------------------+----------------------------------------------+ -| Doesn't Exist | Exists | openstack | -+----------------------+------------------------+----------------------------------------------+ +**Which CLI do I change?** -When adding or updating an ``openstack`` networking command, changes may -first be required to the OpenStack Python SDK to support the underlying -networking resource object, properties and/or actions. Once the OpenStack -Python SDK changes are merged, the related OSC changes can be merged. -The OSC changes may require an update to the OSC openstacksdk version in the ++----------------------+------------------------+-------------------------------------------------+ +| ``neutron`` Command | ``openstack`` Command | CLI to Change | ++======================+========================+=================================================+ +| Exists | Doesn't Exist | ``neutron`` | ++----------------------+------------------------+-------------------------------------------------+ +| Exists | In Progress | ``neutron`` and ``openstack`` | +| | | (update related blueprint or bug) | ++----------------------+------------------------+-------------------------------------------------+ +| Exists | Exists | ``openstack`` | +| | | (assumes command parity resulting in | +| | | ``neutron`` being deprecated) | ++----------------------+------------------------+-------------------------------------------------+ +| Doesn't Exist | Doesn't Exist | ``openstack`` | ++----------------------+------------------------+-------------------------------------------------+ + +**Where does my CLI belong?** + ++---------------------------+-------------------+-------------------------------------------------+ +| Networking Commands | OSC Plugin | OpenStack Project for ``openstack`` Commands | ++===========================+===================+=================================================+ +| Core (Stable) | No | python-openstackclient | ++---------------------------+-------------------+-------------------------------------------------+ +| Core (New/Experimental) | Yes | python-neutronclient | +| | | (with possible move to python-openstackclient) | ++---------------------------+-------------------+-------------------------------------------------+ +| LBaaS v2 | Yes | neutron-lbaas | ++---------------------------+-------------------+-------------------------------------------------+ +| VPNaaS v2 | Yes | neutron-vpnaas | ++---------------------------+-------------------+-------------------------------------------------+ +| FWaaS v2 | Yes | neutron-fwaas | ++---------------------------+-------------------+-------------------------------------------------+ +| LBaaS v1 | N/A | None (deprecated) | ++---------------------------+-------------------+-------------------------------------------------+ +| FWaaS v1 | N/A | None (deprecated) | ++---------------------------+-------------------+-------------------------------------------------+ +| Other | Yes | Applicable project owning networking resource | ++---------------------------+-------------------+-------------------------------------------------+ + + +The following network resources are part of the "Core (Stable)" group: + +- availability zone +- extension +- floating ip +- network +- port +- quota +- rbac +- router +- security group +- security group rule +- subnet +- subnet pool + + +When adding or updating an ``openstack`` networking command to +python-openstackclient, changes may first be required to the +OpenStack Python SDK to support the underlying networking resource object, +properties and/or actions. Once the OpenStack Python SDK changes are merged, +the related OSC changes can be merged. The OSC changes may require an update +to the OSC openstacksdk version in the `requirements.txt `_ -file. +file. ``openstack`` networking commands outside python-openstackclient +are encouraged but not required to use the OpenStack Python SDK. -Neutron stadium users of the neutron client's command extensions must adopt the +When adding an ``openstack`` networking command to python-openstackclient, +you can optionally propose an +`OSC command spec `_ +which documents the new command interface before proceeding with the implementation. + +Users of the neutron client's command extensions must adopt the `OSC plugin system `_ for this transition. Such users will maintain their OSC plugin within their own project and should follow the guidance in the table above to determine -which CLI to change. +which command to change. Developer References -------------------- -* See `OSC neutron-client blueprint `_ - to determine if an ``openstack`` command is in progress. See the ``Related bugs`` list. +* See `OSC neutron support etherpad `_ + to determine if an ``openstack`` command is in progress. * See `OSC command list `_ to determine if an ``openstack`` command exists. +* See `OSC command spec list `_ + to determine if an ``openstack`` command spec exists. * See `OSC plugin command list `_ to determine if an ``openstack`` plugin command exists. * See `OSC command structure `_ @@ -178,5 +227,6 @@ Developer References for guidance on creating new OSC command interfaces. * See `OSC plugin `_ for information on the OSC plugin system to be used for ``neutron`` CLI extensions. +* Create an OSC blueprint: https://blueprints.launchpad.net/python-openstackclient/ * Report an OSC bug: https://bugs.launchpad.net/python-openstackclient/+filebug * Report an OpenStack Python SDK bug: https://bugs.launchpad.net/python-openstacksdk/+filebug From b667b32af4a8b635cbe3f5a3fa8a231b6114a138 Mon Sep 17 00:00:00 2001 From: Haim Daniel Date: Sun, 8 Nov 2015 13:20:45 +0200 Subject: [PATCH 44/57] add rbac support for qos-policies APIImpact Partial-implements: rfe role-based access control for qos policies Related Blueprint: rbac-qos Related-bug: #1512587 Depends-On: I1c59073daa181005a3e878bc2fe033a0709fbf31 Change-Id: If11a10da617e279b19c17d995b7ab89be8cd9427 --- neutronclient/neutron/v2_0/rbac.py | 35 +++++++++++++------ neutronclient/tests/unit/test_cli20_rbac.py | 34 ++++++++++++------ ...dd-rbac-qos-type-support-c42e31fadd7b.yaml | 8 +++++ test-requirements.txt | 1 + 4 files changed, 57 insertions(+), 21 deletions(-) create mode 100644 releasenotes/notes/add-rbac-qos-type-support-c42e31fadd7b.yaml diff --git a/neutronclient/neutron/v2_0/rbac.py b/neutronclient/neutron/v2_0/rbac.py index d840af5..46f254a 100644 --- a/neutronclient/neutron/v2_0/rbac.py +++ b/neutronclient/neutron/v2_0/rbac.py @@ -16,20 +16,31 @@ from neutronclient._i18n import _ from neutronclient.neutron import v2_0 as neutronV20 +# key=object_type: value={key=resource, value=cmd_resource} +RBAC_OBJECTS = {'network': {'network': 'network'}, + 'qos-policy': {'policy': 'qos_policy'}} -def get_rbac_object_id(client, obj_type, obj_id_or_name): - if obj_type == 'network': - obj_id = neutronV20.find_resourceid_by_name_or_id(client, - 'network', - obj_id_or_name) - return obj_id + +def _get_cmd_resource(obj_type): + resource = list(RBAC_OBJECTS[obj_type])[0] + cmd_resource = RBAC_OBJECTS[obj_type][resource] + return resource, cmd_resource + + +def get_rbac_obj_params(client, obj_type, obj_id_or_name): + resource, cmd_resource = _get_cmd_resource(obj_type) + obj_id = neutronV20.find_resourceid_by_name_or_id( + client=client, resource=resource, name_or_id=obj_id_or_name, + cmd_resource=cmd_resource) + + return obj_id, cmd_resource class ListRBACPolicy(neutronV20.ListCommand): """List RBAC policies that belong to a given tenant.""" resource = 'rbac_policy' - list_columns = ['id', 'object_id'] + list_columns = ['id', 'object_type', 'object_id'] pagination_support = True sorting_support = True allow_names = False @@ -53,7 +64,7 @@ class CreateRBACPolicy(neutronV20.CreateCommand): metavar='RBAC_OBJECT', help=_('ID or name of the RBAC object.')) parser.add_argument( - '--type', choices=['network'], + '--type', choices=RBAC_OBJECTS.keys(), required=True, help=_('Type of the object that RBAC policy affects.')) parser.add_argument( @@ -67,11 +78,13 @@ class CreateRBACPolicy(neutronV20.CreateCommand): def args2body(self, parsed_args): neutron_client = self.get_client() - _object_id = get_rbac_object_id(neutron_client, parsed_args.type, - parsed_args.name) + neutron_client.format = parsed_args.request_format + _object_id, _object_type = get_rbac_obj_params(neutron_client, + parsed_args.type, + parsed_args.name) body = { 'object_id': _object_id, - 'object_type': parsed_args.type, + 'object_type': _object_type, 'target_tenant': parsed_args.target_tenant, 'action': parsed_args.action, } diff --git a/neutronclient/tests/unit/test_cli20_rbac.py b/neutronclient/tests/unit/test_cli20_rbac.py index 1d287ba..ca7ea46 100644 --- a/neutronclient/tests/unit/test_cli20_rbac.py +++ b/neutronclient/tests/unit/test_cli20_rbac.py @@ -15,41 +15,54 @@ import sys +import testscenarios + from neutronclient.neutron.v2_0 import rbac from neutronclient.tests.unit import test_cli20 +load_tests = testscenarios.load_tests_apply_scenarios -class CLITestV20RBACJSON(test_cli20.CLITestV20Base): +class CLITestV20RBACBaseJSON(test_cli20.CLITestV20Base): non_admin_status_resources = ['rbac_policy'] + scenarios = [ + ('network rbac objects', + {'object_type_name': 'network', 'object_type_val': 'network'}), + ('qos policy rbac objects', + {'object_type_name': 'qos-policy', 'object_type_val': 'qos_policy'}), + ] + def test_create_rbac_policy_with_mandatory_params(self): - # Create rbac: rbac_object --type network --action access_as_shared + # Create rbac: rbac_object --type --action + # access_as_shared resource = 'rbac_policy' cmd = rbac.CreateRBACPolicy(test_cli20.MyApp(sys.stdout), None) name = 'rbac_object' myid = 'myid' - args = [name, '--type', 'network', + args = [name, '--type', self.object_type_name, '--action', 'access_as_shared'] position_names = ['object_id', 'object_type', 'target_tenant', 'action'] - position_values = [name, 'network', None, 'access_as_shared'] + position_values = [name, self.object_type_val, None, + 'access_as_shared'] self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) def test_create_rbac_policy_with_all_params(self): - # Create rbac: rbac_object --type network --target-tenant tenant_id - # --action access_as_external + # Create rbac: rbac_object --type + # --target-tenant tenant_id --action access_as_external resource = 'rbac_policy' cmd = rbac.CreateRBACPolicy(test_cli20.MyApp(sys.stdout), None) name = 'rbac_object' myid = 'myid' - args = [name, '--type', 'network', + args = [name, '--type', self.object_type_name, '--target-tenant', 'tenant_id', '--action', 'access_as_external'] position_names = ['object_id', 'object_type', 'target_tenant', 'action'] - position_values = [name, 'network', 'tenant_id', 'access_as_external'] + position_values = [name, self.object_type_val, 'tenant_id', + 'access_as_external'] self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) @@ -59,12 +72,13 @@ class CLITestV20RBACJSON(test_cli20.CLITestV20Base): cmd = rbac.CreateRBACPolicy(test_cli20.MyApp(sys.stdout), None) name = u'\u7f51\u7edc' myid = 'myid' - args = [name, '--type', 'network', + args = [name, '--type', self.object_type_name, '--target-tenant', 'tenant_id', '--action', 'access_as_external'] position_names = ['object_id', 'object_type', 'target_tenant', 'action'] - position_values = [name, 'network', 'tenant_id', 'access_as_external'] + position_values = [name, self.object_type_val, 'tenant_id', + 'access_as_external'] self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) diff --git a/releasenotes/notes/add-rbac-qos-type-support-c42e31fadd7b.yaml b/releasenotes/notes/add-rbac-qos-type-support-c42e31fadd7b.yaml new file mode 100644 index 0000000..9dff77c --- /dev/null +++ b/releasenotes/notes/add-rbac-qos-type-support-c42e31fadd7b.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + CLI support for QoS policy RBAC. + + * The ``rbac-create`` command include a --type qos-policy + option. + * The ``rbac-list`` command output includes a new 'type' column. diff --git a/test-requirements.txt b/test-requirements.txt index 992d131..7bece79 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -16,4 +16,5 @@ requests-mock>=0.7.0 # Apache-2.0 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT +testscenarios>=0.4 # Apache-2.0/BSD tempest-lib>=0.14.0 # Apache-2.0 From a147631309cfc57f47f74fc9721f07db85d3fe65 Mon Sep 17 00:00:00 2001 From: Nikolas Hermanns Date: Fri, 12 Feb 2016 11:21:25 +0100 Subject: [PATCH 45/57] Misleading output when network is not found Output is: Unable to find network with name '7ea795c9-0b72-4585-bd91-9bf83c0d1c80' Output should be: Unable to find network with name or id Someone could think that the net id is not checked. Closes-Bug: #1544907 Change-Id: I6999ab4e07a58751a9860764447c00fe7bf8ee81 --- neutronclient/neutron/v2_0/__init__.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index f3f4669..d740aac 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -118,9 +118,17 @@ def find_resource_by_name_or_id(client, resource, name_or_id, return find_resource_by_id(client, resource, name_or_id, cmd_resource, parent_id, fields) except exceptions.NotFound: - return _find_resource_by_name(client, resource, name_or_id, - project_id, cmd_resource, parent_id, - fields) + try: + return _find_resource_by_name(client, resource, name_or_id, + project_id, cmd_resource, parent_id, + fields) + except exceptions.NotFound: + not_found_message = (_("Unable to find %(resource)s with name " + "or id '%(name_or_id)s'") % + {'resource': resource, + 'name_or_id': name_or_id}) + raise exceptions.NotFound( + message=not_found_message) def find_resourceid_by_name_or_id(client, resource, name_or_id, From c422c2610ed219f84e4f9cfaa8ffdf5689999e7e Mon Sep 17 00:00:00 2001 From: Stephen Balukoff Date: Sat, 29 Aug 2015 02:52:41 -0700 Subject: [PATCH 46/57] LBaaS updates to reflect shared pools feature These updates allow the CLI user to fully take advantage of the shared pools functionality being added to Neutron LBaaS in preparation for L7 switching functionality. The Neutron LBaaS patch is here: https://review.openstack.org/#/c/218560/ Partially-Implements: blueprint lbaas-l7-rules Change-Id: Ib72d743ff0d6ca7f0fde034a8c73029308ca01a1 --- neutronclient/neutron/v2_0/lb/v2/listener.py | 55 +++++++++++------ neutronclient/neutron/v2_0/lb/v2/pool.py | 61 +++++++++++++++---- .../tests/unit/lb/v2/test_cli20_listener.py | 44 ++++++++++++- .../tests/unit/lb/v2/test_cli20_pool.py | 48 +++++++++++++-- ...shared-pools-support-6f79b565afad3e47.yaml | 8 +++ 5 files changed, 179 insertions(+), 37 deletions(-) create mode 100644 releasenotes/notes/add-shared-pools-support-6f79b565afad3e47.yaml diff --git a/neutronclient/neutron/v2_0/lb/v2/listener.py b/neutronclient/neutron/v2_0/lb/v2/listener.py index 4a41719..b63e2c2 100644 --- a/neutronclient/neutron/v2_0/lb/v2/listener.py +++ b/neutronclient/neutron/v2_0/lb/v2/listener.py @@ -16,18 +16,27 @@ # from neutronclient._i18n import _ +from neutronclient.common import exceptions from neutronclient.common import utils from neutronclient.neutron import v2_0 as neutronV20 def _get_loadbalancer_id(client, lb_id_or_name): return neutronV20.find_resourceid_by_name_or_id( - client, - 'loadbalancer', - lb_id_or_name, + client, 'loadbalancer', lb_id_or_name, cmd_resource='lbaas_loadbalancer') +def _get_pool(client, pool_id_or_name): + return neutronV20.find_resource_by_name_or_id( + client, 'pool', pool_id_or_name, cmd_resource='lbaas_pool') + + +def _get_pool_id(client, pool_id_or_name): + return neutronV20.find_resourceid_by_name_or_id( + client, 'pool', pool_id_or_name, cmd_resource='lbaas_pool') + + class ListListener(neutronV20.ListCommand): """LBaaS v2 List listeners that belong to a given tenant.""" @@ -64,7 +73,8 @@ class CreateListener(neutronV20.CreateCommand): help=_('Description of the listener.')) parser.add_argument( '--name', - help=_('The name of the listener.')) + help=_('The name of the listener. At least one of --default-pool ' + 'or --loadbalancer must be specified.')) parser.add_argument( '--default-tls-container-ref', dest='default_tls_container_ref', @@ -75,9 +85,11 @@ class CreateListener(neutronV20.CreateCommand): dest='sni_container_refs', nargs='+', help=_('List of TLS container references for SNI.')) + parser.add_argument( + '--default-pool', + help=_('Default pool for the listener.')) parser.add_argument( '--loadbalancer', - required=True, metavar='LOADBALANCER', help=_('ID or name of the load balancer.')) parser.add_argument( @@ -93,22 +105,29 @@ class CreateListener(neutronV20.CreateCommand): help=_('Protocol port for the listener.')) def args2body(self, parsed_args): + resource = { + 'protocol': parsed_args.protocol, + 'protocol_port': parsed_args.protocol_port, + 'admin_state_up': parsed_args.admin_state + } + if not parsed_args.loadbalancer and not parsed_args.default_pool: + message = _('Either --default-pool or --loadbalancer must be ' + 'specified.') + raise exceptions.CommandError(message) if parsed_args.loadbalancer: - parsed_args.loadbalancer = _get_loadbalancer_id( - self.get_client(), - parsed_args.loadbalancer) - body = {'loadbalancer_id': parsed_args.loadbalancer, - 'protocol': parsed_args.protocol, - 'protocol_port': parsed_args.protocol_port, - 'admin_state_up': parsed_args.admin_state} + loadbalancer_id = _get_loadbalancer_id( + self.get_client(), parsed_args.loadbalancer) + resource['loadbalancer_id'] = loadbalancer_id + if parsed_args.default_pool: + default_pool_id = _get_pool_id( + self.get_client(), parsed_args.default_pool) + resource['default_pool_id'] = default_pool_id - neutronV20.update_dict(parsed_args, body, + neutronV20.update_dict(parsed_args, resource, ['connection_limit', 'description', - 'loadbalancer_id', 'name', - 'default_tls_container_ref', - 'sni_container_refs', - 'tenant_id']) - return {self.resource: body} + 'name', 'default_tls_container_ref', + 'sni_container_refs', 'tenant_id']) + return {self.resource: resource} class UpdateListener(neutronV20.UpdateCommand): diff --git a/neutronclient/neutron/v2_0/lb/v2/pool.py b/neutronclient/neutron/v2_0/lb/v2/pool.py index 2745091..6cd0b8b 100644 --- a/neutronclient/neutron/v2_0/lb/v2/pool.py +++ b/neutronclient/neutron/v2_0/lb/v2/pool.py @@ -1,6 +1,7 @@ # Copyright 2013 Mirantis Inc. # Copyright 2014 Blue Box Group, Inc. # Copyright 2015 Hewlett-Packard Development Company, L.P. +# Copyright 2015 Blue Box, an IBM Company # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -17,10 +18,27 @@ # from neutronclient._i18n import _ +from neutronclient.common import exceptions from neutronclient.common import utils from neutronclient.neutron import v2_0 as neutronV20 +def _get_loadbalancer_id(client, lb_id_or_name): + return neutronV20.find_resourceid_by_name_or_id( + client, 'loadbalancer', lb_id_or_name, + cmd_resource='lbaas_loadbalancer') + + +def _get_listener(client, listener_id_or_name): + return neutronV20.find_resource_by_name_or_id( + client, 'listener', listener_id_or_name) + + +def _get_listener_id(client, listener_id_or_name): + return neutronV20.find_resourceid_by_name_or_id( + client, 'listener', listener_id_or_name) + + class ListPool(neutronV20.ListCommand): """LBaaS v2 List pools that belong to a given tenant.""" @@ -70,16 +88,22 @@ class CreatePool(neutronV20.CreateCommand): 'cookie name')) parser.add_argument( '--name', help=_('The name of the pool.')) + parser.add_argument( + '--listener', + help=_('Listener whose default-pool should be set to this pool. ' + 'At least one of --listener or --loadbalancer must be ' + 'specified.')) + parser.add_argument( + '--loadbalancer', + help=_('Loadbalancer with which this pool should be associated. ' + 'At least one of --listener or --loadbalancer must be ' + 'specified.')) parser.add_argument( '--lb-algorithm', required=True, choices=['ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP'], help=_('The algorithm used to distribute load between the members ' 'of the pool.')) - parser.add_argument( - '--listener', - required=True, - help=_('The listener to associate with the pool')) parser.add_argument( '--protocol', required=True, @@ -88,16 +112,29 @@ class CreatePool(neutronV20.CreateCommand): help=_('Protocol for balancing.')) def args2body(self, parsed_args): - _listener_id = neutronV20.find_resourceid_by_name_or_id( - self.get_client(), 'listener', parsed_args.listener) - body = {'admin_state_up': parsed_args.admin_state, - 'protocol': parsed_args.protocol, - 'lb_algorithm': parsed_args.lb_algorithm, - 'listener_id': _listener_id} - neutronV20.update_dict(parsed_args, body, + resource = { + 'admin_state_up': parsed_args.admin_state, + 'protocol': parsed_args.protocol, + 'lb_algorithm': parsed_args.lb_algorithm + } + if not parsed_args.listener and not parsed_args.loadbalancer: + message = _('At least one of --listener or --loadbalancer must be ' + 'specified.') + raise exceptions.CommandError(message) + if parsed_args.listener: + listener_id = _get_listener_id( + self.get_client(), + parsed_args.listener) + resource['listener_id'] = listener_id + if parsed_args.loadbalancer: + loadbalancer_id = _get_loadbalancer_id( + self.get_client(), + parsed_args.loadbalancer) + resource['loadbalancer_id'] = loadbalancer_id + neutronV20.update_dict(parsed_args, resource, ['description', 'name', 'session_persistence', 'tenant_id']) - return {self.resource: body} + return {self.resource: resource} class UpdatePool(neutronV20.UpdateCommand): diff --git a/neutronclient/tests/unit/lb/v2/test_cli20_listener.py b/neutronclient/tests/unit/lb/v2/test_cli20_listener.py index 77d62e0..581e511 100644 --- a/neutronclient/tests/unit/lb/v2/test_cli20_listener.py +++ b/neutronclient/tests/unit/lb/v2/test_cli20_listener.py @@ -16,14 +16,15 @@ import sys +from neutronclient.common import exceptions from neutronclient.neutron.v2_0.lb.v2 import listener from neutronclient.tests.unit import test_cli20 class CLITestV20LbListenerJSON(test_cli20.CLITestV20Base): - def test_create_listener_with_mandatory_params(self): - # lbaas-listener-create with mandatory params only. + def test_create_listener_with_loadbalancer(self): + # lbaas-listener-create with --loadbalancer resource = 'listener' cmd_resource = 'lbaas_listener' cmd = listener.CreateListener(test_cli20.MyApp(sys.stdout), None) @@ -40,6 +41,41 @@ class CLITestV20LbListenerJSON(test_cli20.CLITestV20Base): position_names, position_values, cmd_resource=cmd_resource) + def test_create_listener_with_default_pool(self): + # lbaas-listener-create with --default-pool and no --loadbalancer. + resource = 'listener' + cmd_resource = 'lbaas_listener' + cmd = listener.CreateListener(test_cli20.MyApp(sys.stdout), None) + my_id = 'my-id' + default_pool_id = 'default-pool' + protocol = 'TCP' + protocol_port = '80' + args = ['--protocol', protocol, '--protocol-port', protocol_port, + '--default-pool', default_pool_id] + position_names = ['protocol', 'protocol_port', 'default_pool_id'] + position_values = [protocol, protocol_port, default_pool_id, + True] + self._test_create_resource(resource, cmd, '', my_id, args, + position_names, position_values, + cmd_resource=cmd_resource) + + def test_create_listener_with_no_loadbalancer_or_default_pool(self): + # lbaas-listener-create without --default-pool or --loadbalancer. + resource = 'listener' + cmd_resource = 'lbaas_listener' + cmd = listener.CreateListener(test_cli20.MyApp(sys.stdout), None) + my_id = 'my-id' + protocol = 'TCP' + protocol_port = '80' + args = ['--protocol', protocol, '--protocol-port', protocol_port] + position_names = ['protocol', 'protocol_port'] + position_values = [protocol, protocol_port, True] + self._test_create_resource(resource, cmd, '', my_id, args, + position_names, position_values, + cmd_resource=cmd_resource, + no_api_call=True, + expected_exception=exceptions.CommandError) + def test_create_listener_with_all_params(self): # lbaas-listener-create with all params set. resource = 'listener' @@ -47,6 +83,7 @@ class CLITestV20LbListenerJSON(test_cli20.CLITestV20Base): cmd = listener.CreateListener(test_cli20.MyApp(sys.stdout), None) my_id = 'my-id' loadbalancer = 'loadbalancer' + default_pool_id = 'default-pool' protocol = 'TCP' protocol_port = '80' connection_limit = 10 @@ -54,14 +91,17 @@ class CLITestV20LbListenerJSON(test_cli20.CLITestV20Base): args = ['--admin-state-down', '--protocol', protocol, '--protocol-port', protocol_port, '--loadbalancer', loadbalancer, + '--default-pool', default_pool_id, '--default-tls-container-ref', def_tls_cont_ref, '--sni-container-refs', '1111', '2222', '3333', '--connection-limit', '10'] position_names = ['admin_state_up', 'protocol', 'protocol_port', 'loadbalancer_id', + 'default_pool_id', 'default_tls_container_ref', 'sni_container_refs', 'connection_limit'] position_values = [False, protocol, protocol_port, loadbalancer, + default_pool_id, def_tls_cont_ref, ['1111', '2222', '3333'], connection_limit] self._test_create_resource(resource, cmd, '', my_id, args, diff --git a/neutronclient/tests/unit/lb/v2/test_cli20_pool.py b/neutronclient/tests/unit/lb/v2/test_cli20_pool.py index efaa065..2e5787b 100644 --- a/neutronclient/tests/unit/lb/v2/test_cli20_pool.py +++ b/neutronclient/tests/unit/lb/v2/test_cli20_pool.py @@ -16,14 +16,15 @@ import sys +from neutronclient.common import exceptions from neutronclient.neutron.v2_0.lb.v2 import pool from neutronclient.tests.unit import test_cli20 class CLITestV20LbPoolJSON(test_cli20.CLITestV20Base): - def test_create_pool_with_mandatory_params(self): - # lbaas-pool-create with mandatory params only. + def test_create_pool_with_listener(self): + # lbaas-pool-create with listener resource = 'pool' cmd_resource = 'lbaas_pool' cmd = pool.CreatePool(test_cli20.MyApp(sys.stdout), None) @@ -40,6 +41,41 @@ class CLITestV20LbPoolJSON(test_cli20.CLITestV20Base): position_names, position_values, cmd_resource=cmd_resource) + def test_create_pool_with_loadbalancer_no_listener(self): + """lbaas-pool-create with loadbalancer, no listener.""" + resource = 'pool' + cmd_resource = 'lbaas_pool' + cmd = pool.CreatePool(test_cli20.MyApp(sys.stdout), None) + my_id = 'my-id' + lb_algorithm = 'ROUND_ROBIN' + loadbalancer = 'loadbalancer' + protocol = 'TCP' + args = ['--lb-algorithm', lb_algorithm, '--protocol', protocol, + '--loadbalancer', loadbalancer] + position_names = ['admin_state_up', 'lb_algorithm', 'protocol', + 'loadbalancer_id'] + position_values = [True, lb_algorithm, protocol, loadbalancer] + self._test_create_resource(resource, cmd, '', my_id, args, + position_names, position_values, + cmd_resource=cmd_resource) + + def test_create_pool_with_no_listener_or_loadbalancer(self): + """lbaas-pool-create with no listener or loadbalancer.""" + resource = 'pool' + cmd_resource = 'lbaas_pool' + cmd = pool.CreatePool(test_cli20.MyApp(sys.stdout), None) + my_id = 'my-id' + lb_algorithm = 'ROUND_ROBIN' + protocol = 'TCP' + args = ['--lb-algorithm', lb_algorithm, '--protocol', protocol] + position_names = ['admin_state_up', 'lb_algorithm', 'protocol'] + position_values = [True, lb_algorithm, protocol] + self._test_create_resource(resource, cmd, '', my_id, args, + position_names, position_values, + cmd_resource=cmd_resource, + no_api_call=True, + expected_exception=exceptions.CommandError) + def test_create_pool_with_all_params(self): # lbaas-pool-create with all params set. resource = 'pool' @@ -48,6 +84,7 @@ class CLITestV20LbPoolJSON(test_cli20.CLITestV20Base): my_id = 'my-id' lb_algorithm = 'ROUND_ROBIN' listener = 'listener' + loadbalancer = 'loadbalancer' protocol = 'TCP' description = 'description' session_persistence_str = 'type=APP_COOKIE,cookie_name=1234' @@ -57,12 +94,13 @@ class CLITestV20LbPoolJSON(test_cli20.CLITestV20Base): args = ['--lb-algorithm', lb_algorithm, '--protocol', protocol, '--description', description, '--session-persistence', session_persistence_str, '--admin-state-down', '--name', name, - '--listener', listener] + '--listener', listener, '--loadbalancer', loadbalancer] position_names = ['lb_algorithm', 'protocol', 'description', 'session_persistence', 'admin_state_up', 'name', - 'listener_id'] + 'listener_id', 'loadbalancer_id'] position_values = [lb_algorithm, protocol, description, - session_persistence, False, name, listener] + session_persistence, False, name, listener, + loadbalancer] self._test_create_resource(resource, cmd, '', my_id, args, position_names, position_values, cmd_resource=cmd_resource) diff --git a/releasenotes/notes/add-shared-pools-support-6f79b565afad3e47.yaml b/releasenotes/notes/add-shared-pools-support-6f79b565afad3e47.yaml new file mode 100644 index 0000000..1be2e0e --- /dev/null +++ b/releasenotes/notes/add-shared-pools-support-6f79b565afad3e47.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + CLI support for Neutron-LBaaS v2 shared pools added. + + * Pools can be created independently from listeners. + * Listeners can share the same default_pool. + * Makes Layer 7 switching support much more useful. From a64aad2c0ffe84e15d59b5df03b0685e37094a45 Mon Sep 17 00:00:00 2001 From: Evgeny Fedoruk Date: Wed, 26 Aug 2015 08:35:29 -0700 Subject: [PATCH 47/57] Reflecting L7 content rules capability in LBaaS Adds CLI commands for L7 policies and rules Change-Id: I3617c7ecd2a3ac0cae555893235e34d6c2135b81 Implements: blueprint lbaas-l7-rules Co-Authored-By: Evgeny Fedoruk Co-Authored-By: Stephen Balukoff --- neutronclient/neutron/v2_0/lb/v2/l7policy.py | 155 +++++++++++ neutronclient/neutron/v2_0/lb/v2/l7rule.py | 148 ++++++++++ neutronclient/shell.py | 12 + .../tests/unit/lb/v2/test_cli20_l7policy.py | 260 ++++++++++++++++++ .../tests/unit/lb/v2/test_cli20_l7rule.py | 210 ++++++++++++++ neutronclient/v2_0/client.py | 64 +++++ ...-policies-capability-0f17cd06f044c83c.yaml | 8 + 7 files changed, 857 insertions(+) create mode 100644 neutronclient/neutron/v2_0/lb/v2/l7policy.py create mode 100644 neutronclient/neutron/v2_0/lb/v2/l7rule.py create mode 100644 neutronclient/tests/unit/lb/v2/test_cli20_l7policy.py create mode 100644 neutronclient/tests/unit/lb/v2/test_cli20_l7rule.py create mode 100644 releasenotes/notes/add-l7-content-policies-capability-0f17cd06f044c83c.yaml diff --git a/neutronclient/neutron/v2_0/lb/v2/l7policy.py b/neutronclient/neutron/v2_0/lb/v2/l7policy.py new file mode 100644 index 0000000..26c3315 --- /dev/null +++ b/neutronclient/neutron/v2_0/lb/v2/l7policy.py @@ -0,0 +1,155 @@ +# Copyright 2016 Radware LTD. +# 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. +# + +from neutronclient._i18n import _ +from neutronclient.common import exceptions +from neutronclient.common import utils +from neutronclient.neutron import v2_0 as neutronV20 + + +def _get_listener_id(client, listener_id_or_name): + return neutronV20.find_resourceid_by_name_or_id( + client, 'listener', listener_id_or_name) + + +def _get_pool_id(client, pool_id_or_name): + return neutronV20.find_resourceid_by_name_or_id( + client, 'pool', pool_id_or_name, cmd_resource='lbaas_pool') + + +def _add_common_args(parser, is_create=True): + parser.add_argument( + '--name', + help=_('Name of the policy.')) + parser.add_argument( + '--description', + help=_('Description of the policy.')) + parser.add_argument( + '--action', + required=is_create, + metavar='ACTION', + type=utils.convert_to_uppercase, + choices=['REJECT', 'REDIRECT_TO_POOL', 'REDIRECT_TO_URL'], + help=_('Action type of the policy.')) + parser.add_argument( + '--redirect-pool', + help=_('ID or name of the pool for REDIRECT_TO_POOL action type.')) + parser.add_argument( + '--redirect-url', + help=_('URL for REDIRECT_TO_URL action type. ' + 'This should be a valid URL string.')) + parser.add_argument( + '--position', + type=int, + help=_('L7 policy position in ordered policies list. ' + 'This must be an integer starting from 1. ' + 'Not specifying the position will place the policy ' + 'at the tail of existing policies list.')) + + +def _common_args2body(client, parsed_args, is_create=True): + if parsed_args.redirect_url: + if parsed_args.action != 'REDIRECT_TO_URL': + raise exceptions.CommandError(_('Action must be REDIRECT_TO_URL')) + if parsed_args.redirect_pool: + if parsed_args.action != 'REDIRECT_TO_POOL': + raise exceptions.CommandError(_('Action must be REDIRECT_TO_POOL')) + parsed_args.redirect_pool_id = _get_pool_id( + client, parsed_args.redirect_pool) + if (parsed_args.action == 'REDIRECT_TO_URL' and + not parsed_args.redirect_url): + raise exceptions.CommandError(_('Redirect URL must be specified')) + if (parsed_args.action == 'REDIRECT_TO_POOL' and + not parsed_args.redirect_pool): + raise exceptions.CommandError(_('Redirect pool must be specified')) + + attributes = ['name', 'description', + 'action', 'redirect_pool_id', 'redirect_url', + 'position', 'admin_state_up'] + if is_create: + parsed_args.listener_id = _get_listener_id( + client, parsed_args.listener) + attributes.extend(['listener_id', 'tenant_id']) + body = {} + neutronV20.update_dict(parsed_args, body, attributes) + return {'l7policy': body} + + +class ListL7Policy(neutronV20.ListCommand): + """LBaaS v2 List L7 policies that belong to a given listener.""" + + resource = 'l7policy' + shadow_resource = 'lbaas_l7policy' + pagination_support = True + sorting_support = True + list_columns = [ + 'id', 'name', 'action', 'redirect_pool_id', 'redirect_url', + 'position', 'admin_state_up', 'status' + ] + + +class ShowL7Policy(neutronV20.ShowCommand): + """LBaaS v2 Show information of a given L7 policy.""" + + resource = 'l7policy' + shadow_resource = 'lbaas_l7policy' + + +class CreateL7Policy(neutronV20.CreateCommand): + """LBaaS v2 Create L7 policy.""" + + resource = 'l7policy' + shadow_resource = 'lbaas_l7policy' + + def add_known_arguments(self, parser): + _add_common_args(parser) + parser.add_argument( + '--admin-state-down', + dest='admin_state_up', + action='store_false', + help=_('Set admin state up to false.')) + parser.add_argument( + '--listener', + required=True, + metavar='LISTENER', + help=_('ID or name of the listener this policy belongs to.')) + + def args2body(self, parsed_args): + return _common_args2body(self.get_client(), parsed_args) + + +class UpdateL7Policy(neutronV20.UpdateCommand): + """LBaaS v2 Update a given L7 policy.""" + + resource = 'l7policy' + shadow_resource = 'lbaas_l7policy' + + def add_known_arguments(self, parser): + _add_common_args(parser, is_create=False) + utils.add_boolean_argument( + parser, '--admin-state-up', + help=_('Specify the administrative state of the policy' + ' (True meaning "Up").')) + + def args2body(self, parsed_args): + return _common_args2body(self.get_client(), parsed_args, False) + + +class DeleteL7Policy(neutronV20.DeleteCommand): + """LBaaS v2 Delete a given L7 policy.""" + + resource = 'l7policy' + shadow_resource = 'lbaas_l7policy' diff --git a/neutronclient/neutron/v2_0/lb/v2/l7rule.py b/neutronclient/neutron/v2_0/lb/v2/l7rule.py new file mode 100644 index 0000000..1286559 --- /dev/null +++ b/neutronclient/neutron/v2_0/lb/v2/l7rule.py @@ -0,0 +1,148 @@ +# Copyright 2016 Radware LTD. +# 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. +# + + +from neutronclient._i18n import _ +from neutronclient.common import utils +from neutronclient.neutron import v2_0 as neutronV20 + + +def _get_policy_id(client, policy_id_or_name): + return neutronV20.find_resourceid_by_name_or_id( + client, 'l7policy', policy_id_or_name, + cmd_resource='lbaas_l7policy') + + +class LbaasL7RuleMixin(object): + + def set_extra_attrs(self, parsed_args): + self.parent_id = _get_policy_id(self.get_client(), + parsed_args.l7policy) + + def add_known_arguments(self, parser): + parser.add_argument( + 'l7policy', metavar='L7POLICY', + help=_('ID or name of L7 policy this rule belongs to.')) + + +def _add_common_args(parser, is_create=True): + parser.add_argument( + '--type', + required=is_create, + type=utils.convert_to_uppercase, + choices=['HOST_NAME', 'PATH', 'FILE_TYPE', 'HEADER', 'COOKIE'], + help=_('Rule type.')) + parser.add_argument( + '--compare-type', + required=is_create, + type=utils.convert_to_uppercase, + choices=['REGEX', 'STARTS_WITH', 'ENDS_WITH', + 'CONTAINS', 'EQUAL_TO'], + help=_('Rule compare type.')) + parser.add_argument( + '--invert-compare', + dest='invert', + action='store_true', + help=_('Invert the compare type.')) + parser.add_argument( + '--key', + help=_('Key to compare.' + ' Relevant for HEADER and COOKIE types only.')) + parser.add_argument( + '--value', + required=is_create, + help=_('Value to compare.')) + + +def _common_args2body(client, parsed_args, is_create=True): + attributes = ['type', 'compare_type', + 'invert', 'key', 'value', 'admin_state_up'] + if is_create: + attributes.append('tenant_id') + body = {} + neutronV20.update_dict(parsed_args, body, attributes) + return {'rule': body} + + +class ListL7Rule(LbaasL7RuleMixin, neutronV20.ListCommand): + """LBaaS v2 List L7 rules that belong to a given L7 policy.""" + + resource = 'rule' + shadow_resource = 'lbaas_l7rule' + pagination_support = True + sorting_support = True + + list_columns = [ + 'id', 'type', 'compare_type', 'invert', 'key', 'value', + 'admin_state_up', 'status' + ] + + def take_action(self, parsed_args): + self.parent_id = _get_policy_id(self.get_client(), + parsed_args.l7policy) + self.values_specs.append('--l7policy_id=%s' % self.parent_id) + return super(ListL7Rule, self).take_action(parsed_args) + + +class ShowL7Rule(LbaasL7RuleMixin, neutronV20.ShowCommand): + """LBaaS v2 Show information of a given rule.""" + + resource = 'rule' + shadow_resource = 'lbaas_l7rule' + + +class CreateL7Rule(LbaasL7RuleMixin, neutronV20.CreateCommand): + """LBaaS v2 Create L7 rule.""" + + resource = 'rule' + shadow_resource = 'lbaas_l7rule' + + def add_known_arguments(self, parser): + super(CreateL7Rule, self).add_known_arguments(parser) + _add_common_args(parser) + parser.add_argument( + '--admin-state-down', + dest='admin_state_up', + action='store_false', + help=_('Set admin state up to false')) + + def args2body(self, parsed_args): + return _common_args2body(self.get_client(), parsed_args) + + +class UpdateL7Rule(LbaasL7RuleMixin, neutronV20.UpdateCommand): + """LBaaS v2 Update a given L7 rule.""" + + resource = 'rule' + shadow_resource = 'lbaas_l7rule' + + def add_known_arguments(self, parser): + super(UpdateL7Rule, self).add_known_arguments(parser) + _add_common_args(parser, False) + utils.add_boolean_argument( + parser, '--admin-state-up', + help=_('Specify the administrative state of the rule' + ' (True meaning "Up").')) + + def args2body(self, parsed_args): + return _common_args2body(self.get_client(), parsed_args, False) + + +class DeleteL7Rule(LbaasL7RuleMixin, neutronV20.DeleteCommand): + """LBaaS v2 Delete a given L7 rule.""" + + resource = 'rule' + shadow_resource = 'lbaas_l7rule' diff --git a/neutronclient/shell.py b/neutronclient/shell.py index 795571a..fef1134 100644 --- a/neutronclient/shell.py +++ b/neutronclient/shell.py @@ -59,6 +59,8 @@ from neutronclient.neutron.v2_0.lb import healthmonitor as lb_healthmonitor from neutronclient.neutron.v2_0.lb import member as lb_member from neutronclient.neutron.v2_0.lb import pool as lb_pool from neutronclient.neutron.v2_0.lb.v2 import healthmonitor as lbaas_healthmon +from neutronclient.neutron.v2_0.lb.v2 import l7policy as lbaas_l7policy +from neutronclient.neutron.v2_0.lb.v2 import l7rule as lbaas_l7rule from neutronclient.neutron.v2_0.lb.v2 import listener as lbaas_listener from neutronclient.neutron.v2_0.lb.v2 import loadbalancer as lbaas_loadbalancer from neutronclient.neutron.v2_0.lb.v2 import member as lbaas_member @@ -217,6 +219,16 @@ COMMAND_V2 = { 'lbaas-listener-create': lbaas_listener.CreateListener, 'lbaas-listener-update': lbaas_listener.UpdateListener, 'lbaas-listener-delete': lbaas_listener.DeleteListener, + 'lbaas-l7policy-list': lbaas_l7policy.ListL7Policy, + 'lbaas-l7policy-show': lbaas_l7policy.ShowL7Policy, + 'lbaas-l7policy-create': lbaas_l7policy.CreateL7Policy, + 'lbaas-l7policy-update': lbaas_l7policy.UpdateL7Policy, + 'lbaas-l7policy-delete': lbaas_l7policy.DeleteL7Policy, + 'lbaas-l7rule-list': lbaas_l7rule.ListL7Rule, + 'lbaas-l7rule-show': lbaas_l7rule.ShowL7Rule, + 'lbaas-l7rule-create': lbaas_l7rule.CreateL7Rule, + 'lbaas-l7rule-update': lbaas_l7rule.UpdateL7Rule, + 'lbaas-l7rule-delete': lbaas_l7rule.DeleteL7Rule, 'lbaas-pool-list': lbaas_pool.ListPool, 'lbaas-pool-show': lbaas_pool.ShowPool, 'lbaas-pool-create': lbaas_pool.CreatePool, diff --git a/neutronclient/tests/unit/lb/v2/test_cli20_l7policy.py b/neutronclient/tests/unit/lb/v2/test_cli20_l7policy.py new file mode 100644 index 0000000..7186931 --- /dev/null +++ b/neutronclient/tests/unit/lb/v2/test_cli20_l7policy.py @@ -0,0 +1,260 @@ +# Copyright 2016 Radware LTD. +# 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 sys + +from neutronclient.common import exceptions +from neutronclient.neutron.v2_0.lb.v2 import l7policy +from neutronclient.tests.unit import test_cli20 + +"""Structure for mapping cli and api arguments + +The structure maps cli arguments and a list of its +api argument name, default cli value and default api value. +It helps to make tests more general for different argument types. +""" +args_conf = { + 'name': ['name', 'test_policy', 'test_policy'], + 'description': ['description', 'test policy', 'test policy'], + 'listener': ['listener_id', 'test_listener', 'test_listener'], + 'admin-state-up': ['admin_state_up', True, True], + 'admin-state-down': ['admin_state_up', None, False], + 'action': ['action', 'REJECT', 'REJECT'], + 'redirect-url': ['redirect_url', 'http://url', 'http://url'], + 'redirect-pool': ['redirect_pool_id', 'test_pool', 'test_pool'], + 'position': ['position', '1', 1]} + + +class CLITestV20LbL7PolicyJSON(test_cli20.CLITestV20Base): + + def _get_test_args(self, *args, **kwargs): + """Function for generically building testing arguments""" + cli_args = [] + api_args = {} + for arg in args: + cli_args.append('--' + arg) + if not args_conf[arg][1]: + pass + elif arg in kwargs: + cli_args.append(str(kwargs[arg])) + else: + cli_args.append(args_conf[arg][1]) + + if arg in kwargs: + api_args[args_conf[arg][0]] = kwargs[arg] + else: + api_args[args_conf[arg][0]] = args_conf[arg][2] + + return cli_args, api_args + + def _test_create_policy(self, *args, **kwargs): + resource = 'l7policy' + cmd_resource = 'lbaas_l7policy' + cmd = l7policy.CreateL7Policy(test_cli20.MyApp(sys.stdout), None) + cli_args, api_args = self._get_test_args(*args, **kwargs) + position_names = list(api_args.keys()) + position_values = list(api_args.values()) + self._test_create_resource(resource, cmd, None, 'test_id', + cli_args, position_names, position_values, + cmd_resource=cmd_resource) + + def _test_update_policy(self, *args, **kwargs): + resource = 'l7policy' + cmd_resource = 'lbaas_l7policy' + cmd = l7policy.UpdateL7Policy(test_cli20.MyApp(sys.stdout), None) + cli_args, api_args = self._get_test_args(*args, **kwargs) + cli_args.append('test_id') + self._test_update_resource(resource, cmd, 'test_id', + cli_args, api_args, + cmd_resource=cmd_resource) + + def test_create_policy_with_mandatory_params(self): + # lbaas-l7policy-create with mandatory params only. + self._test_create_policy('action', 'listener') + + def test_create_policy_with_all_params(self): + # lbaas-l7policy-create REJECT policy. + self._test_create_policy('name', 'description', + 'action', 'listener', + 'position') + + def test_create_disabled_policy(self): + # lbaas-l7policy-create disabled REJECT policy. + self._test_create_policy('action', 'listener', 'admin-state-down') + + def test_create_url_redirect_policy(self): + # lbaas-l7policy-create REDIRECT_TO_URL policy. + self._test_create_policy('name', 'description', + 'action', 'listener', + 'redirect-url', + action='REDIRECT_TO_URL') + + def test_create_url_redirect_policy_no_url(self): + # lbaas-l7policy-create REDIRECT_TO_URL policy without url argument. + self.assertRaises(exceptions.CommandError, + self._test_create_policy, + 'name', 'description', + 'action', 'listener', + action='REDIRECT_TO_URL') + + def test_create_pool_redirect_policy(self): + # lbaas-l7policy-create REDIRECT_TO_POOL policy. + self._test_create_policy('name', 'description', + 'action', 'listener', + 'redirect-pool', + action='REDIRECT_TO_POOL') + + def test_create_pool_redirect_policy_no_pool(self): + # lbaas-l7policy-create REDIRECT_TO_POOL policy without pool argument. + self.assertRaises(exceptions.CommandError, + self._test_create_policy, + 'name', 'description', + 'action', 'listener', + action='REDIRECT_TO_POOL') + + def test_create_reject_policy_with_url(self): + # lbaas-l7policy-create REJECT policy while specifying url argument. + self.assertRaises(exceptions.CommandError, + self._test_create_policy, + 'action', 'listener', + 'redirect-url') + + def test_create_reject_policy_with_pool(self): + # lbaas-l7policy-create REJECT policy while specifying pool argument. + self.assertRaises(exceptions.CommandError, + self._test_create_policy, + 'action', 'listener', + 'redirect-pool') + + def test_create_pool_redirect_policy_with_url(self): + # lbaas-l7policy-create REDIRECT_TO_POOL policy with url argument. + self.assertRaises(exceptions.CommandError, + self._test_create_policy, + 'action', 'listener', + 'redirect-pool', 'redirect-url', + action='REDIRECT_TO_POOL') + + def test_create_url_redirect_policy_with_pool(self): + # lbaas-l7policy-create REDIRECT_TO_URL policy with pool argument. + self.assertRaises(exceptions.CommandError, + self._test_create_policy, + 'action', 'listener', + 'redirect-pool', 'redirect-url', + action='REDIRECT_TO_URL') + + def test_list_policies(self): + # lbaas-l7policy-list. + + resources = 'l7policies' + cmd_resources = 'lbaas_l7policies' + cmd = l7policy.ListL7Policy(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, True, + cmd_resources=cmd_resources) + + def test_list_policies_pagination(self): + # lbaas-l7policy-list with pagination. + + resources = 'l7policies' + cmd_resources = 'lbaas_l7policies' + cmd = l7policy.ListL7Policy(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources_with_pagination( + resources, cmd, cmd_resources=cmd_resources) + + def test_list_policies_sort(self): + # lbaas-l7policy-list --sort-key id --sort-key asc. + + resources = 'l7policies' + cmd_resources = 'lbaas_l7policies' + cmd = l7policy.ListL7Policy(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources( + resources, cmd, True, cmd_resources=cmd_resources) + + def test_list_policies_limit(self): + # lbaas-l7policy-list -P. + + resources = 'l7policies' + cmd_resources = 'lbaas_l7policies' + cmd = l7policy.ListL7Policy(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources( + resources, cmd, page_size=1000, cmd_resources=cmd_resources) + + def test_show_policy_id(self): + # lbaas-l7policy-show test_id. + + resource = 'l7policy' + cmd_resource = 'lbaas_l7policy' + cmd = l7policy.ShowL7Policy(test_cli20.MyApp(sys.stdout), None) + args = ['--fields', 'test_id', self.test_id] + self._test_show_resource( + resource, cmd, self.test_id, args, + ['test_id'], cmd_resource=cmd_resource) + + def test_show_policy_id_name(self): + # lbaas-l7policy-show. + + resource = 'l7policy' + cmd_resource = 'lbaas_l7policy' + cmd = l7policy.ShowL7Policy(test_cli20.MyApp(sys.stdout), None) + args = ['--fields', 'test_id', '--fields', 'name', self.test_id] + self._test_show_resource( + resource, cmd, self.test_id, args, + ['test_id', 'name'], cmd_resource=cmd_resource) + + def test_disable_policy(self): + # lbaas-l7policy-update test_id --admin-state-up False. + + self._test_update_policy('admin-state-up', + **{'admin-state-up': 'False'}) + + def test_update_policy_name_and_description(self): + # lbaas-l7policy-update test_id --name other --description other_desc. + + self._test_update_policy('name', 'description', + name='name', + description='other desc') + + def test_update_pool_redirect_policy(self): + # lbaas-l7policy-update test_id --action REDIRECT_TO_POOL + # --redirect-pool id. + + self._test_update_policy('action', 'redirect-pool', + **{'action': 'REDIRECT_TO_POOL', + 'redirect-pool': 'id'}) + + def test_update_url_redirect_policy(self): + # lbaas-l7policy-update test_id --action REDIRECT_TO_URL + # --redirect-url http://other_url. + + self._test_update_policy('action', 'redirect-url', + **{'action': 'REDIRECT_TO_URL', + 'redirect-url': 'http://other_url'}) + + def test_update_policy_position(self): + # lbaas-l7policy-update test_id --position 2. + + self._test_update_policy('position', + position=2) + + def test_delete_policy(self): + # lbaas-l7policy-delete test_id. + + resource = 'l7policy' + cmd_resource = 'lbaas_l7policy' + cmd = l7policy.DeleteL7Policy(test_cli20.MyApp(sys.stdout), None) + test_id = 'test_id' + args = [test_id] + self._test_delete_resource(resource, cmd, test_id, args, + cmd_resource=cmd_resource) diff --git a/neutronclient/tests/unit/lb/v2/test_cli20_l7rule.py b/neutronclient/tests/unit/lb/v2/test_cli20_l7rule.py new file mode 100644 index 0000000..5483054 --- /dev/null +++ b/neutronclient/tests/unit/lb/v2/test_cli20_l7rule.py @@ -0,0 +1,210 @@ +# Copyright 2016 Radware LTD. +# 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 sys + +from neutronclient.neutron.v2_0.lb.v2 import l7rule +from neutronclient.tests.unit import test_cli20 + + +"""Structure for mapping cli and api arguments + +The structure maps cli arguments and a list of its +api argument name, default cli value and default api value. +It helps to make tests more general for different argument types. +""" +args_conf = { + 'admin-state-up': ['admin_state_up', True, True], + 'admin-state-down': ['admin_state_up', None, False], + 'type': ['type', 'HOST_NAME', 'HOST_NAME'], + 'compare-type': ['compare_type', 'EQUAL_TO', 'EQUAL_TO'], + 'invert-compare': ['invert', None, True], + 'key': ['key', 'key', 'key'], + 'value': ['value', 'value', 'value']} + + +class CLITestV20LbL7RuleJSON(test_cli20.CLITestV20Base): + + def _get_test_args(self, *args, **kwargs): + """Function for generically building testing arguments""" + cli_args = [] + api_args = {} + for arg in args: + cli_args.append('--' + arg) + if not args_conf[arg][1]: + pass + elif arg in kwargs: + cli_args.append(str(kwargs[arg])) + else: + cli_args.append(args_conf[arg][1]) + + if arg in kwargs: + api_args[args_conf[arg][0]] = kwargs[arg] + else: + api_args[args_conf[arg][0]] = args_conf[arg][2] + + if 'invert' not in api_args: + api_args['invert'] = False + return cli_args, api_args + + def _test_create_rule(self, *args, **kwargs): + resource = 'rule' + cmd_resource = 'lbaas_l7rule' + cmd = l7rule.CreateL7Rule(test_cli20.MyApp(sys.stdout), None) + cli_args, api_args = self._get_test_args(*args, **kwargs) + position_names = list(api_args.keys()) + position_values = list(api_args.values()) + cli_args.append('test_policy') + self._test_create_resource(resource, cmd, None, 'test_id', + cli_args, position_names, position_values, + cmd_resource=cmd_resource, + parent_id='test_policy') + + def _test_update_rule(self, *args, **kwargs): + resource = 'rule' + cmd_resource = 'lbaas_l7rule' + cmd = l7rule.UpdateL7Rule(test_cli20.MyApp(sys.stdout), None) + cli_args, api_args = self._get_test_args(*args, **kwargs) + cli_args.append('test_id') + cli_args.append('test_policy') + self._test_update_resource(resource, cmd, 'test_id', + cli_args, api_args, + cmd_resource=cmd_resource, + parent_id='test_policy') + + def test_create_rule_with_mandatory_params(self): + # lbaas-l7rule-create with mandatory params only. + + self._test_create_rule('type', 'compare-type', + 'value') + + def test_create_disabled_rule(self): + # lbaas-l7rule-create disabled rule. + + self._test_create_rule('type', 'compare-type', + 'value', 'admin-state-down') + + def test_create_rule_with_all_params(self): + # lbaas-l7rule-create with all params set. + + self._test_create_rule('type', 'compare-type', + 'invert-compare', 'key', 'value', + type='HEADER', compare_type='CONTAINS', + key='other_key', value='other_value') + + def test_create_rule_with_inverted_compare(self): + # lbaas-l7rule-create with invertted compare type. + + self._test_create_rule('type', 'compare-type', + 'invert-compare', 'value') + + def test_list_rules(self): + # lbaas-l7rule-list. + + resources = 'rules' + cmd_resources = 'lbaas_l7rules' + cmd = l7rule.ListL7Rule(test_cli20.MyApp(sys.stdout), None) + + policy_id = 'policy_id' + self._test_list_resources(resources, cmd, True, + base_args=[policy_id], + cmd_resources=cmd_resources, + parent_id=policy_id, + query="l7policy_id=%s" % policy_id) + + def test_list_rules_pagination(self): + # lbaas-l7rule-list with pagination. + + resources = 'rules' + cmd_resources = 'lbaas_l7rules' + cmd = l7rule.ListL7Rule(test_cli20.MyApp(sys.stdout), None) + policy_id = 'policy_id' + self._test_list_resources_with_pagination( + resources, cmd, base_args=[policy_id], + cmd_resources=cmd_resources, parent_id=policy_id, + query="l7policy_id=%s" % policy_id) + + def test_list_rules_sort(self): + # lbaas-l7rule-list --sort-key id --sort-key asc. + + resources = 'rules' + cmd_resources = 'lbaas_l7rules' + cmd = l7rule.ListL7Rule(test_cli20.MyApp(sys.stdout), None) + policy_id = 'policy_id' + self._test_list_resources( + resources, cmd, True, base_args=[policy_id], + cmd_resources=cmd_resources, parent_id=policy_id, + query="l7policy_id=%s" % policy_id) + + def test_list_rules_limit(self): + # lbaas-l7rule-list -P. + + resources = 'rules' + cmd_resources = 'lbaas_l7rules' + cmd = l7rule.ListL7Rule(test_cli20.MyApp(sys.stdout), None) + policy_id = 'policy_id' + self._test_list_resources(resources, cmd, page_size=1000, + base_args=[policy_id], + cmd_resources=cmd_resources, + parent_id=policy_id, + query="l7policy_id=%s" % policy_id) + + def test_show_rule_id(self): + # lbaas-l7rule-show test_id. + + resource = 'rule' + cmd_resource = 'lbaas_l7rule' + policy_id = 'policy_id' + cmd = l7rule.ShowL7Rule(test_cli20.MyApp(sys.stdout), None) + args = ['--fields', 'id', self.test_id, policy_id] + self._test_show_resource(resource, cmd, self.test_id, args, ['id'], + cmd_resource=cmd_resource, + parent_id=policy_id) + + def test_update_rule_type(self): + # lbaas-l7rule-update test_id --type HEADER test_policy + + self._test_update_rule('type', type='HEADER') + + def test_update_rule_compare_type(self): + # lbaas-l7rule-update test_id --compare-type CONTAINS test_policy. + + self._test_update_rule('compare-type', + **{'compare-type': 'CONTAINS'}) + + def test_update_rule_inverted_compare_type(self): + # lbaas-l7rule-update test_id --invert-compare test_policy. + + self._test_update_rule('invert-compare') + + def test_update_rule_key_value(self): + # lbaas-l7rule-update test_id --key other --value other test_policy. + + self._test_update_rule('key', 'value', + key='other', value='other') + + def test_delete_rule(self): + # lbaas-l7rule-delete test_id policy_id. + + resource = 'rule' + cmd_resource = 'lbaas_l7rule' + policy_id = 'policy_id' + test_id = 'test_id' + cmd = l7rule.DeleteL7Rule(test_cli20.MyApp(sys.stdout), None) + args = [test_id, policy_id] + self._test_delete_resource(resource, cmd, test_id, args, + cmd_resource=cmd_resource, + parent_id=policy_id) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index 2c4f105..bbbb3e7 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -339,6 +339,10 @@ class Client(ClientBase): lbaas_loadbalancer_path_status = "/lbaas/loadbalancers/%s/statuses" lbaas_listeners_path = "/lbaas/listeners" lbaas_listener_path = "/lbaas/listeners/%s" + lbaas_l7policies_path = "/lbaas/l7policies" + lbaas_l7policy_path = lbaas_l7policies_path + "/%s" + lbaas_l7rules_path = lbaas_l7policy_path + "/rules" + lbaas_l7rule_path = lbaas_l7rules_path + "/%s" lbaas_pools_path = "/lbaas/pools" lbaas_pool_path = "/lbaas/pools/%s" lbaas_healthmonitors_path = "/lbaas/healthmonitors" @@ -438,6 +442,9 @@ class Client(ClientBase): 'metering_label_rules': 'metering_label_rule', 'loadbalancers': 'loadbalancer', 'listeners': 'listener', + 'l7rules': 'l7rule', + 'l7policies': 'l7policy', + 'lbaas_l7policies': 'lbaas_l7policy', 'lbaas_pools': 'lbaas_pool', 'lbaas_healthmonitors': 'lbaas_healthmonitor', 'lbaas_members': 'lbaas_member', @@ -447,6 +454,7 @@ class Client(ClientBase): 'qos_policies': 'qos_policy', 'policies': 'policy', 'bandwidth_limit_rules': 'bandwidth_limit_rule', + 'rules': 'rule', 'rule_types': 'rule_type', 'flavors': 'flavor', 'bgp_speakers': 'bgp_speaker', @@ -984,6 +992,62 @@ class Client(ClientBase): """Deletes the specified lbaas_listener.""" return self.delete(self.lbaas_listener_path % (lbaas_listener)) + @APIParamsCall + def list_lbaas_l7policies(self, retrieve_all=True, **_params): + """Fetches a list of all L7 policies for a listener.""" + return self.list('l7policies', self.lbaas_l7policies_path, + retrieve_all, **_params) + + @APIParamsCall + def show_lbaas_l7policy(self, l7policy, **_params): + """Fetches information of a certain listener's L7 policy.""" + return self.get(self.lbaas_l7policy_path % l7policy, + params=_params) + + @APIParamsCall + def create_lbaas_l7policy(self, body=None): + """Creates L7 policy for a certain listener.""" + return self.post(self.lbaas_l7policies_path, body=body) + + @APIParamsCall + def update_lbaas_l7policy(self, l7policy, body=None): + """Updates L7 policy.""" + return self.put(self.lbaas_l7policy_path % l7policy, + body=body) + + @APIParamsCall + def delete_lbaas_l7policy(self, l7policy): + """Deletes the specified L7 policy.""" + return self.delete(self.lbaas_l7policy_path % l7policy) + + @APIParamsCall + def list_lbaas_l7rules(self, l7policy, retrieve_all=True, **_params): + """Fetches a list of all rules for L7 policy.""" + return self.list('rules', self.lbaas_l7rules_path % l7policy, + retrieve_all, **_params) + + @APIParamsCall + def show_lbaas_l7rule(self, l7rule, l7policy, **_params): + """Fetches information of a certain L7 policy's rule.""" + return self.get(self.lbaas_l7rule_path % (l7policy, l7rule), + params=_params) + + @APIParamsCall + def create_lbaas_l7rule(self, l7policy, body=None): + """Creates rule for a certain L7 policy.""" + return self.post(self.lbaas_l7rules_path % l7policy, body=body) + + @APIParamsCall + def update_lbaas_l7rule(self, l7rule, l7policy, body=None): + """Updates L7 rule.""" + return self.put(self.lbaas_l7rule_path % (l7policy, l7rule), + body=body) + + @APIParamsCall + def delete_lbaas_l7rule(self, l7rule, l7policy): + """Deletes the specified L7 rule.""" + return self.delete(self.lbaas_l7rule_path % (l7policy, l7rule)) + @APIParamsCall def list_lbaas_pools(self, retrieve_all=True, **_params): """Fetches a list of all lbaas_pools for a tenant.""" diff --git a/releasenotes/notes/add-l7-content-policies-capability-0f17cd06f044c83c.yaml b/releasenotes/notes/add-l7-content-policies-capability-0f17cd06f044c83c.yaml new file mode 100644 index 0000000..e413df7 --- /dev/null +++ b/releasenotes/notes/add-l7-content-policies-capability-0f17cd06f044c83c.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + CLI support for Layer 7 content policies and rules. + + * L7 policies can be defined for listeners along + with the ability to set L7 policy order. + * Multiple rules can be created for an L7 policy. From 88fbd3870cf3872fb0c9b4269503c62458664b3b Mon Sep 17 00:00:00 2001 From: John Davidge Date: Mon, 15 Feb 2016 10:02:54 -0800 Subject: [PATCH 48/57] Support cleanup of tenant resources with a single API call The addition of the 'neutron purge' command allows cloud admins to conveniently delete multiple neutron resources associated with a given tenant. The command will delete all supported resources provided that they can be deleted (not in use, etc) and feedback the amount of each resource deleted to the user. A completion percentage is also given to keep the user informed of progress. Currently supports deletion of: Networks Subnets (implicitly) Routers Ports (including router interfaces) Floating IPs Security Groups This feature can be easily extended to support more resource types in the future. DocImpact: Update API documentation to describe neutron-purge usage Change-Id: I5a366d3537191045eb53f9cccd8cd0f7ce54a63b Closes-Bug: 1511574 Partially-Implements: blueprint tenant-delete --- neutronclient/neutron/v2_0/purge.py | 147 +++++++++++++++ neutronclient/shell.py | 2 + .../tests/functional/core/test_purge.py | 172 ++++++++++++++++++ neutronclient/tests/unit/test_cli20_purge.py | 100 ++++++++++ .../add-neutron-purge-a89e3d1179dce4b1.yaml | 16 ++ 5 files changed, 437 insertions(+) create mode 100644 neutronclient/neutron/v2_0/purge.py create mode 100644 neutronclient/tests/functional/core/test_purge.py create mode 100644 neutronclient/tests/unit/test_cli20_purge.py create mode 100644 releasenotes/notes/add-neutron-purge-a89e3d1179dce4b1.yaml diff --git a/neutronclient/neutron/v2_0/purge.py b/neutronclient/neutron/v2_0/purge.py new file mode 100644 index 0000000..6d8e9d9 --- /dev/null +++ b/neutronclient/neutron/v2_0/purge.py @@ -0,0 +1,147 @@ +# Copyright 2016 Cisco Systems +# 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 sys + +from neutronclient._i18n import _ +from neutronclient.neutron import v2_0 as neutronV20 + + +class Purge(neutronV20.NeutronCommand): + + def _pluralize(self, string): + return string + 's' + + def _get_resources(self, neutron_client, resource_types, tenant_id): + resources = [] + for resource_type in resource_types: + resources.append([]) + resource_type_plural = self._pluralize(resource_type) + opts = {'fields': ['id', 'tenant_id']} + if resource_type_plural == 'ports': + opts['fields'].append('device_id') + opts['fields'].append('device_owner') + function = getattr(neutron_client, 'list_%s' % + resource_type_plural) + if callable(function): + returned_resources = function(**opts).get(resource_type_plural, + []) + for resource in returned_resources: + if resource['tenant_id'] == tenant_id: + index = resource_types.index(resource_type) + resources[index].append(resource) + self.total_resources += 1 + return resources + + def _delete_resource(self, neutron_client, resource_type, resource): + resource_id = resource['id'] + if resource_type == 'port': + if resource.get('device_owner', '') == 'network:router_interface': + body = {'port_id': resource_id} + neutron_client.remove_interface_router(resource['device_id'], + body) + return + function = getattr(neutron_client, 'delete_%s' % resource_type) + if callable(function): + function(resource_id) + + def _purge_resources(self, neutron_client, resource_types, + tenant_resources): + deleted = {} + failed = {} + failures = False + for resources in tenant_resources: + index = tenant_resources.index(resources) + resource_type = resource_types[index] + failed[resource_type] = 0 + deleted[resource_type] = 0 + for resource in resources: + try: + self._delete_resource(neutron_client, resource_type, + resource) + deleted[resource_type] += 1 + self.deleted_resources += 1 + except Exception: + failures = True + failed[resource_type] += 1 + self.total_resources -= 1 + percent_complete = 100 + if self.total_resources > 0: + percent_complete = (self.deleted_resources / + float(self.total_resources)) * 100 + sys.stdout.write("\rPurging resources: %d%% complete." % + percent_complete) + sys.stdout.flush() + return (deleted, failed, failures) + + def _build_message(self, deleted, failed, failures): + msg = '' + deleted_msg = [] + for resource, value in deleted.items(): + if value: + if not msg: + msg = 'Deleted' + if not value == 1: + resource = self._pluralize(resource) + deleted_msg.append(" %d %s" % (value, resource)) + if deleted_msg: + msg += ','.join(deleted_msg) + + failed_msg = [] + if failures: + if msg: + msg += '. ' + msg += 'The following resources could not be deleted:' + for resource, value in failed.items(): + if value: + if not value == 1: + resource = self._pluralize(resource) + failed_msg.append(" %d %s" % (value, resource)) + msg += ','.join(failed_msg) + + if msg: + msg += '.' + else: + msg = _('Tenant has no supported resources.') + + return msg + + def get_parser(self, prog_name): + parser = super(Purge, self).get_parser(prog_name) + parser.add_argument( + 'tenant', metavar='TENANT', + help=_('ID of Tenant owning the resources to be deleted.')) + return parser + + def run(self, parsed_args): + neutron_client = self.get_client() + + self.any_failures = False + + # A list of the types of resources supported in the order in which + # they should be deleted. + resource_types = ['floatingip', 'port', 'router', + 'network', 'security_group'] + + deleted = {} + failed = {} + self.total_resources = 0 + self.deleted_resources = 0 + resources = self._get_resources(neutron_client, resource_types, + parsed_args.tenant) + deleted, failed, failures = self._purge_resources(neutron_client, + resource_types, + resources) + print('\n%s' % self._build_message(deleted, failed, failures)) diff --git a/neutronclient/shell.py b/neutronclient/shell.py index c1f6b0a..037e2be 100644 --- a/neutronclient/shell.py +++ b/neutronclient/shell.py @@ -66,6 +66,7 @@ from neutronclient.neutron.v2_0 import network from neutronclient.neutron.v2_0.nsx import networkgateway from neutronclient.neutron.v2_0.nsx import qos_queue from neutronclient.neutron.v2_0 import port +from neutronclient.neutron.v2_0 import purge from neutronclient.neutron.v2_0.qos import bandwidth_limit_rule from neutronclient.neutron.v2_0.qos import policy as qos_policy from neutronclient.neutron.v2_0.qos import rule as qos_rule @@ -171,6 +172,7 @@ COMMAND_V2 = { 'port-create': port.CreatePort, 'port-delete': port.DeletePort, 'port-update': port.UpdatePort, + 'purge': purge.Purge, 'quota-list': quota.ListQuota, 'quota-show': quota.ShowQuota, 'quota-delete': quota.DeleteQuota, diff --git a/neutronclient/tests/functional/core/test_purge.py b/neutronclient/tests/functional/core/test_purge.py new file mode 100644 index 0000000..91d92a9 --- /dev/null +++ b/neutronclient/tests/functional/core/test_purge.py @@ -0,0 +1,172 @@ +# Copyright 2016 Cisco Systems +# 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. + +from neutronclient.tests.functional import base + +from tempest_lib import exceptions + + +class PurgeNeutronClientCLITest(base.ClientTestBase): + + def _safe_cleanup(self, delete_command): + try: + self.neutron(delete_command) + except exceptions.CommandFailed: + # This resource was already purged successfully + pass + + def _create_subnet(self, name, tenant_id, cidr): + params = ('%(name)s --name %(name)s --tenant-id %(tenant)s ' + '%(cidr)s' % {'name': name, + 'tenant': tenant_id, + 'cidr': cidr}) + subnet = self.parser.listing(self.neutron('subnet-create', + params=params)) + for row in subnet: + if row['Field'] == 'id': + return row['Value'] + + def _create_router(self, name, tenant_id): + params = ('%(name)s --tenant_id %(tenant)s' % {'name': name, + 'tenant': tenant_id}) + router = self.parser.listing(self.neutron('router-create', + params=params)) + for row in router: + if row['Field'] == 'id': + return row['Value'] + + def _create_floatingip(self, network, tenant_id): + params = ('%(network)s --tenant-id %(tenant)s' % + {'network': network, 'tenant': tenant_id}) + floatingip = self.parser.listing(self.neutron('floatingip-create', + params=params)) + for row in floatingip: + if row['Field'] == 'id': + return row['Value'] + + def _create_resources(self, name, tenant_id, shared_tenant_id=None): + # If no shared_tenant_id is provided, create the resources for the + # current tenant to test that they will be deleted when not in use. + if not shared_tenant_id: + shared_tenant_id = tenant_id + + self.neutron('net-create', + params=('%(name)s --router:external True ' + '--tenant-id %(tenant)s' % {'name': name, + 'tenant': tenant_id})) + self.addCleanup(self._safe_cleanup, 'net-delete %s' % name) + + self.neutron('net-create', + params=('%(name)s-shared --shared ' + '--tenant-id %(tenant)s' % + {'name': name, 'tenant': shared_tenant_id})) + self.addCleanup(self._safe_cleanup, + 'net-delete %s-shared' % name) + + subnet = self._create_subnet(name, tenant_id, '192.168.71.0/24') + self.addCleanup(self._safe_cleanup, 'subnet-delete %s' % name) + + subnet = self._create_subnet('%s-shared' % name, tenant_id, + '192.168.81.0/24') + self.addCleanup(self._safe_cleanup, 'subnet-delete %s-shared' % name) + + router = self._create_router(name, tenant_id) + self.addCleanup(self._safe_cleanup, 'router-delete %s' % name) + + self.neutron('router-interface-add', + params=('%(router)s %(subnet)s ' + '--tenant-id %(tenant)s' % {'router': router, + 'subnet': subnet, + 'tenant': tenant_id})) + + self.neutron('port-create', + params=('%(name)s --name %(name)s ' + '--tenant-id %(tenant)s' % {'name': name, + 'tenant': tenant_id})) + self.addCleanup(self._safe_cleanup, 'port-delete %s' % name) + + self.neutron('port-create', + params=('%(name)s-shared --name %(name)s-shared ' + '--tenant-id %(tenant)s' % {'name': name, + 'tenant': tenant_id})) + self.addCleanup(self._safe_cleanup, 'port-delete %s-shared' % name) + + self.neutron('security-group-create', + params=('%(name)s --tenant-id %(tenant)s' % + {'name': name, 'tenant': tenant_id})) + self.addCleanup(self._safe_cleanup, 'security-group-delete %s' % name) + + floatingip = self._create_floatingip(name, tenant_id) + self.addCleanup(self._safe_cleanup, ('floatingip-delete ' + '%s' % floatingip)) + return floatingip + + def _verify_deletion(self, resources, resource_type): + purged = True + no_purge_purged = True + for row in resources: + if resource_type == 'port' and row.get('id', None): + port = self.parser.listing(self.neutron('port-show', + params=row['id'])) + port_dict = {} + for row in port: + port_dict[row['Field']] = row['Value'] + if port_dict['device_owner'] == 'network:router_interface': + if port_dict['tenant_id'] == 'purge-tenant': + purged = False + elif port_dict['tenant_id'] == 'no-purge-tenant': + no_purge_purged = False + if not purged or not no_purge_purged: + self.addCleanup(self.neutron, + ('router-interface-delete %(router)s ' + 'port=%(port)s' % + {'router': port_dict['device_id'], + 'port': port_dict['id']})) + if (row.get('name') == 'purge-me' or + row.get('id') == self.purge_floatingip): + purged = False + elif ('no-purge' in row.get('name', '') or + row.get('id') == self.no_purge_floatingip): + no_purge_purged = False + + if not purged: + self.fail('%s not deleted by neutron purge' % resource_type) + + if no_purge_purged: + self.fail('%s owned by another tenant incorrectly deleted ' + 'by neutron purge' % resource_type) + + def test_purge(self): + self.purge_floatingip = self._create_resources('purge-me', + 'purge-tenant') + self.no_purge_floatingip = self._create_resources('no-purge', + 'no-purge-tenant', + 'purge-tenant') + + purge_output = self.neutron('purge', params='purge-tenant').strip() + if not purge_output: + self.fail('Purge command did not return feedback') + + networks = self.parser.listing(self.neutron('net-list')) + subnets = self.parser.listing(self.neutron('subnet-list')) + routers = self.parser.listing(self.neutron('router-list')) + ports = self.parser.listing(self.neutron('port-list')) + floatingips = self.parser.listing(self.neutron('floatingip-list')) + + self._verify_deletion(networks, 'network') + self._verify_deletion(subnets, 'subnet') + self._verify_deletion(ports, 'port') + self._verify_deletion(routers, 'router') + self._verify_deletion(floatingips, 'floatingip') diff --git a/neutronclient/tests/unit/test_cli20_purge.py b/neutronclient/tests/unit/test_cli20_purge.py new file mode 100644 index 0000000..9bfd91c --- /dev/null +++ b/neutronclient/tests/unit/test_cli20_purge.py @@ -0,0 +1,100 @@ +# Copyright 2016 Cisco Systems +# 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 sys + +from neutronclient.neutron.v2_0 import purge +from neutronclient.tests.unit import test_cli20 + + +class CLITestV20Purge(test_cli20.CLITestV20Base): + + def setUp(self): + super(CLITestV20Purge, self).setUp() + self.resource_types = ['floatingip', 'port', 'router', + 'network', 'security_group'] + + def _generate_resources_dict(self, value=0): + resources_dict = {} + resources_dict['true'] = value + for resource_type in self.resource_types: + resources_dict[resource_type] = value + return resources_dict + + def _verify_suffix(self, resources, message): + for resource, value in resources.items(): + if value > 0: + suffix = list('%(value)d %(resource)s' % + {'value': value, 'resource': resource}) + if value != 1: + suffix.append('s') + suffix = ''.join(suffix) + self.assertIn(suffix, message) + else: + self.assertNotIn(resource, message) + + def _verify_message(self, message, deleted, failed): + message = message.split('.') + success_prefix = "Deleted " + failure_prefix = "The following resources could not be deleted: " + if not deleted['true']: + for msg in message: + self.assertNotIn(success_prefix, msg) + message = message[0] + if not failed['true']: + expected = 'Tenant has no supported resources' + self.assertEqual(expected, message) + else: + self.assertIn(failure_prefix, message) + self._verify_suffix(failed, message) + else: + resources_deleted = message[0] + self.assertIn(success_prefix, resources_deleted) + self._verify_suffix(deleted, resources_deleted) + if failed['true']: + resources_failed = message[1] + self.assertIn(failure_prefix, resources_failed) + self._verify_suffix(failed, resources_failed) + else: + for msg in message: + self.assertNotIn(failure_prefix, msg) + + def _verify_result(self, my_purge, deleted, failed): + message = my_purge._build_message(deleted, failed, failed['true']) + self._verify_message(message, deleted, failed) + + def test_build_message(self): + my_purge = purge.Purge(test_cli20.MyApp(sys.stdout), None) + + # Verify message when tenant has no supported resources + deleted = self._generate_resources_dict() + failed = self._generate_resources_dict() + self._verify_result(my_purge, deleted, failed) + + # Verify message when tenant has supported resources, + # and they are all deleteable + deleted = self._generate_resources_dict(1) + self._verify_result(my_purge, deleted, failed) + + # Verify message when tenant has supported resources, + # and some are not deleteable + failed = self._generate_resources_dict(1) + self._verify_result(my_purge, deleted, failed) + + # Verify message when tenant has supported resources, + # and all are not deleteable + deleted = self._generate_resources_dict() + self._verify_result(my_purge, deleted, failed) diff --git a/releasenotes/notes/add-neutron-purge-a89e3d1179dce4b1.yaml b/releasenotes/notes/add-neutron-purge-a89e3d1179dce4b1.yaml new file mode 100644 index 0000000..a689b89 --- /dev/null +++ b/releasenotes/notes/add-neutron-purge-a89e3d1179dce4b1.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + New command 'neutron purge ' will delete all + supported resources owned by the given tenant, provided + that the user has sufficient authorization and the + resources in question are not shared, in use, or + otherwise undeletable. + + Supported resources are: + * Networks + * Subnets + * Routers + * Ports + * Floating IPs + * Security Groups From 8910471df3ca643eac4429ae6d5c82e633a98b1a Mon Sep 17 00:00:00 2001 From: Manjeet Singh Bhatia Date: Tue, 19 Jan 2016 23:26:41 +0000 Subject: [PATCH 49/57] Add commands for Network IP Availability This patch adds commandline for getting details about networks IP availability. Change-Id: Ie02c01ed8c4e291f91ed4cfd8781f4ec6a612cc7 Co-Authored-By: Brandon Logan Co-Authored-By: Akihiro Motoki --- .../neutron/v2_0/network_ip_availability.py | 73 +++++++++++++++++++ neutronclient/shell.py | 3 + .../test_cli20_network_ip_availability.py | 54 ++++++++++++++ neutronclient/v2_0/client.py | 16 ++++ ...work-ip-availability-ac9a462f42fe9db4.yaml | 9 +++ 5 files changed, 155 insertions(+) create mode 100644 neutronclient/neutron/v2_0/network_ip_availability.py create mode 100644 neutronclient/tests/unit/test_cli20_network_ip_availability.py create mode 100644 releasenotes/notes/network-ip-availability-ac9a462f42fe9db4.yaml diff --git a/neutronclient/neutron/v2_0/network_ip_availability.py b/neutronclient/neutron/v2_0/network_ip_availability.py new file mode 100644 index 0000000..d1332a2 --- /dev/null +++ b/neutronclient/neutron/v2_0/network_ip_availability.py @@ -0,0 +1,73 @@ +# 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 cliff import show +import six + +from neutronclient._i18n import _ +from neutronclient.neutron import v2_0 as neutronV20 + + +class ListIpAvailability(neutronV20.ListCommand): + """List IP usage of networks""" + + resource = 'network_ip_availability' + resource_plural = 'network_ip_availabilities' + list_columns = ['network_id', 'network_name', 'total_ips', 'used_ips'] + paginations_support = True + sorting_support = True + + filter_attrs = [ + {'name': 'ip_version', + 'help': _('Returns IP availability for the network subnets ' + 'with a given IP version. Default: 4'), + 'argparse_kwargs': {'type': int, + 'choices': [4, 6], + 'default': 4} + }, + {'name': 'network_id', + 'help': _('Returns IP availability for the network ' + 'matching a given network ID.')}, + {'name': 'network_name', + 'help': _('Returns IP availability for the network ' + 'matching a given name.')}, + {'name': 'tenant_id', + 'help': _('Returns IP availability for the networks ' + 'with a given tenant ID.')}, + ] + + +class ShowIpAvailability(neutronV20.NeutronCommand, show.ShowOne): + """Show IP usage of specific network""" + + resource = 'network_ip_availability' + + def get_parser(self, prog_name): + parser = super(ShowIpAvailability, self).get_parser(prog_name) + parser.add_argument( + 'network_id', metavar='NETWORK', + help=_('ID or name of network to look up.')) + return parser + + def take_action(self, parsed_args): + self.log.debug('run(%s)', parsed_args) + neutron_client = self.get_client() + _id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, 'network', parsed_args.network_id) + data = neutron_client.show_network_ip_availability(_id) + self.format_output_data(data) + resource = data[self.resource] + if self.resource in data: + return zip(*sorted(six.iteritems(resource))) + else: + return None diff --git a/neutronclient/shell.py b/neutronclient/shell.py index 795571a..be42598 100644 --- a/neutronclient/shell.py +++ b/neutronclient/shell.py @@ -66,6 +66,7 @@ from neutronclient.neutron.v2_0.lb.v2 import pool as lbaas_pool from neutronclient.neutron.v2_0.lb import vip as lb_vip from neutronclient.neutron.v2_0 import metering from neutronclient.neutron.v2_0 import network +from neutronclient.neutron.v2_0 import network_ip_availability from neutronclient.neutron.v2_0.nsx import networkgateway from neutronclient.neutron.v2_0.nsx import qos_queue from neutronclient.neutron.v2_0 import port @@ -427,6 +428,8 @@ COMMAND_V2 = { 'bgp-peer-create': bgp_peer.CreatePeer, 'bgp-peer-update': bgp_peer.UpdatePeer, 'bgp-peer-delete': bgp_peer.DeletePeer, + 'net-ip-availability-list': network_ip_availability.ListIpAvailability, + 'net-ip-availability-show': network_ip_availability.ShowIpAvailability, } COMMANDS = {'2.0': COMMAND_V2} diff --git a/neutronclient/tests/unit/test_cli20_network_ip_availability.py b/neutronclient/tests/unit/test_cli20_network_ip_availability.py new file mode 100644 index 0000000..eb325a8 --- /dev/null +++ b/neutronclient/tests/unit/test_cli20_network_ip_availability.py @@ -0,0 +1,54 @@ +# 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 sys + +from neutronclient.neutron.v2_0 import network_ip_availability +from neutronclient.tests.unit import test_cli20 + + +class CLITestV20NetworkIPAvailability(test_cli20.CLITestV20Base): + + id_field = 'network_id' + + def _test_list_network_ip_availability(self, args, query): + resources = "network_ip_availabilities" + cmd = network_ip_availability.ListIpAvailability(test_cli20.MyApp + (sys.stdout), None) + self._test_list_resources(resources, cmd, + base_args=args, + query=query) + + def test_list_network_ip_availability(self): + self._test_list_network_ip_availability(args=None, + query='ip_version=4') + + def test_list_network_ip_availability_ipv6(self): + self._test_list_network_ip_availability( + args=['--ip-version', '6'], query='ip_version=6') + + def test_list_network_ip_availability_net_id_and_ipv4(self): + self._test_list_network_ip_availability( + args=['--ip-version', '4', '--network-id', 'myid'], + query='ip_version=4&network_id=myid') + + def test_list_network_ip_availability_net_name_and_tenant_id(self): + self._test_list_network_ip_availability( + args=['--network-name', 'foo', '--tenant-id', 'mytenant'], + query='network_name=foo&tenant_id=mytenant&ip_version=4') + + def test_show_network_ip_availability(self): + resource = "network_ip_availability" + cmd = network_ip_availability.ShowIpAvailability( + test_cli20.MyApp(sys.stdout), None) + self._test_show_resource(resource, cmd, self.test_id, + args=[self.test_id]) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index 2c4f105..c06f850 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -412,6 +412,8 @@ class Client(ClientBase): bgp_speaker_path = "/bgp-speakers/%s" bgp_peers_path = "/bgp-peers" bgp_peer_path = "/bgp-peers/%s" + network_ip_availabilities_path = '/network-ip-availabilities' + network_ip_availability_path = '/network-ip-availabilities/%s' # API has no way to report plurals, so we have to hard code them EXTED_PLURALS = {'routers': 'router', @@ -451,6 +453,7 @@ class Client(ClientBase): 'flavors': 'flavor', 'bgp_speakers': 'bgp_speaker', 'bgp_peers': 'bgp_peer', + 'network_ip_availabilities': 'network_ip_availability', } @APIParamsCall @@ -1812,6 +1815,19 @@ class Client(ClientBase): """Deletes the specified BGP peer.""" return self.delete(self.bgp_peer_path % peer_id) + @APIParamsCall + def list_network_ip_availabilities(self, retrieve_all=True, **_params): + """Fetches IP availibility information for all networks""" + return self.list('network_ip_availabilities', + self.network_ip_availabilities_path, + retrieve_all, **_params) + + @APIParamsCall + def show_network_ip_availability(self, network, **_params): + """Fetches IP availability information for a specified network""" + return self.get(self.network_ip_availability_path % (network), + params=_params) + def __init__(self, **kwargs): """Initialize a new client for the Neutron v2.0 API.""" super(Client, self).__init__(**kwargs) diff --git a/releasenotes/notes/network-ip-availability-ac9a462f42fe9db4.yaml b/releasenotes/notes/network-ip-availability-ac9a462f42fe9db4.yaml new file mode 100644 index 0000000..7a120fd --- /dev/null +++ b/releasenotes/notes/network-ip-availability-ac9a462f42fe9db4.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + CLI support for network IP availability + + * The ``net-ip-availability-list`` command provides a list of IP + usage statistics for all networks. + * The ``net-ip-availability-show`` command provides IP usage stats + for a specific network. From f67f4af8bd733f5bd186a7c02211ed0667e08a96 Mon Sep 17 00:00:00 2001 From: Hirofumi Ichihara Date: Tue, 1 Mar 2016 12:37:15 +0900 Subject: [PATCH 50/57] Add tags support This patch adds the tag support for CLI. Change-Id: Ia84b873e3916e7b9668181fe14c1448ef608bf1d Partial-Implements: blueprint add-tags-to-core-resources Related-Bug: #1489291 --- neutronclient/neutron/v2_0/__init__.py | 3 + neutronclient/neutron/v2_0/network.py | 21 +++ neutronclient/neutron/v2_0/tag.py | 105 ++++++++++++++ neutronclient/shell.py | 4 + neutronclient/tests/unit/test_cli20_tag.py | 128 ++++++++++++++++++ neutronclient/v2_0/client.py | 22 +++ .../add-tag-support-bad62d60ecc7075c.yaml | 16 +++ 7 files changed, 299 insertions(+) create mode 100644 neutronclient/neutron/v2_0/tag.py create mode 100644 neutronclient/tests/unit/test_cli20_tag.py create mode 100644 releasenotes/notes/add-tag-support-bad62d60ecc7075c.yaml diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index d740aac..9da4828 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -36,6 +36,7 @@ HEX_ELEM = '[0-9A-Fa-f]' UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}', HEX_ELEM + '{4}', HEX_ELEM + '{4}', HEX_ELEM + '{12}']) +HYPHEN_OPTS = ['tags_any', 'not_tags', 'not_tags_any'] def _get_resource_plural(resource, client): @@ -691,6 +692,8 @@ class ListCommand(NeutronCommand, lister.Lister): for field in self.filter_attrs] for attr in filter_attrs: val = getattr(parsed_args, attr, None) + if attr in HYPHEN_OPTS: + attr = attr.replace('_', '-') if val: search_opts[attr] = val return search_opts diff --git a/neutronclient/neutron/v2_0/network.py b/neutronclient/neutron/v2_0/network.py index 2fb26d3..4ec7ace 100644 --- a/neutronclient/neutron/v2_0/network.py +++ b/neutronclient/neutron/v2_0/network.py @@ -60,6 +60,27 @@ class ListNetwork(neutronV20.ListCommand): {'name': 'router:external', 'help': _('Filter and list the networks which are external.'), 'boolean': True}, + {'name': 'tags', + 'help': _("Filter and list %s which has all given tags. " + "Multiple tags can be set like --tags "), + 'boolean': False, + 'argparse_kwargs': {'metavar': 'TAG'}}, + {'name': 'tags_any', + 'help': _("Filter and list %s which has any given tags. " + "Multiple tags can be set like --tags-any "), + 'boolean': False, + 'argparse_kwargs': {'metavar': 'TAG'}}, + {'name': 'not_tags', + 'help': _("Filter and list %s which does not have all given tags. " + "Multiple tags can be set like --not-tags "), + 'boolean': False, + 'argparse_kwargs': {'metavar': 'TAG'}}, + {'name': 'not_tags_any', + 'help': _("Filter and list %s which does not have any given tags. " + "Multiple tags can be set like --not-tags-any " + ""), + 'boolean': False, + 'argparse_kwargs': {'metavar': 'TAG'}}, ] def extend_list(self, data, parsed_args): diff --git a/neutronclient/neutron/v2_0/tag.py b/neutronclient/neutron/v2_0/tag.py new file mode 100644 index 0000000..2507d41 --- /dev/null +++ b/neutronclient/neutron/v2_0/tag.py @@ -0,0 +1,105 @@ +# 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 neutronclient._i18n import _ +from neutronclient.common import exceptions +from neutronclient.neutron import v2_0 as neutronv20 + + +# List of resources can be set tag +TAG_RESOURCES = ['network'] + + +def _convert_resource_args(client, parsed_args): + resource_type = neutronv20._get_resource_plural( + parsed_args.resource_type, client) + resource_id = neutronv20.find_resourceid_by_name_or_id( + client, parsed_args.resource_type, parsed_args.resource) + return resource_type, resource_id + + +def _add_common_arguments(parser): + parser.add_argument('--resource-type', + choices=TAG_RESOURCES, + dest='resource_type', + required=True, + help=_('Resource Type.')) + parser.add_argument('--resource', + required=True, + help=_('Resource name or ID.')) + + +class AddTag(neutronv20.NeutronCommand): + """Add a tag into the resource.""" + + def get_parser(self, prog_name): + parser = super(AddTag, self).get_parser(prog_name) + _add_common_arguments(parser) + parser.add_argument('--tag', + required=True, + help=_('Tag to be added.')) + return parser + + def take_action(self, parsed_args): + client = self.get_client() + resource_type, resource_id = _convert_resource_args(client, + parsed_args) + client.add_tag(resource_type, resource_id, parsed_args.tag) + + +class ReplaceTag(neutronv20.NeutronCommand): + """Replace all tags on the resource.""" + + def get_parser(self, prog_name): + parser = super(ReplaceTag, self).get_parser(prog_name) + _add_common_arguments(parser) + parser.add_argument('--tag', + metavar='TAG', + action='append', + dest='tags', + required=True, + help=_('Tag (This option can be repeated).')) + return parser + + def take_action(self, parsed_args): + client = self.get_client() + resource_type, resource_id = _convert_resource_args(client, + parsed_args) + body = {'tags': parsed_args.tags} + client.replace_tag(resource_type, resource_id, body) + + +class RemoveTag(neutronv20.NeutronCommand): + """Remove a tag on the resource.""" + + def get_parser(self, prog_name): + parser = super(RemoveTag, self).get_parser(prog_name) + _add_common_arguments(parser) + tag_opt = parser.add_mutually_exclusive_group() + tag_opt.add_argument('--all', + action='store_true', + help=_('Remove all tags on the resource.')) + tag_opt.add_argument('--tag', + help=_('Tag to be removed.')) + return parser + + def take_action(self, parsed_args): + if not parsed_args.all and not parsed_args.tag: + raise exceptions.CommandError( + _("--all or --tag must be specified")) + client = self.get_client() + resource_type, resource_id = _convert_resource_args(client, + parsed_args) + if parsed_args.all: + client.remove_tag_all(resource_type, resource_id) + else: + client.remove_tag(resource_type, resource_id, parsed_args.tag) diff --git a/neutronclient/shell.py b/neutronclient/shell.py index bb923c2..5261a34 100644 --- a/neutronclient/shell.py +++ b/neutronclient/shell.py @@ -83,6 +83,7 @@ from neutronclient.neutron.v2_0 import securitygroup from neutronclient.neutron.v2_0 import servicetype from neutronclient.neutron.v2_0 import subnet from neutronclient.neutron.v2_0 import subnetpool +from neutronclient.neutron.v2_0 import tag from neutronclient.neutron.v2_0.vpn import endpoint_group from neutronclient.neutron.v2_0.vpn import ikepolicy from neutronclient.neutron.v2_0.vpn import ipsec_site_connection @@ -444,6 +445,9 @@ COMMAND_V2 = { 'bgp-peer-delete': bgp_peer.DeletePeer, 'net-ip-availability-list': network_ip_availability.ListIpAvailability, 'net-ip-availability-show': network_ip_availability.ShowIpAvailability, + 'tag-add': tag.AddTag, + 'tag-replace': tag.ReplaceTag, + 'tag-remove': tag.RemoveTag, } COMMANDS = {'2.0': COMMAND_V2} diff --git a/neutronclient/tests/unit/test_cli20_tag.py b/neutronclient/tests/unit/test_cli20_tag.py new file mode 100644 index 0000000..24f07b7 --- /dev/null +++ b/neutronclient/tests/unit/test_cli20_tag.py @@ -0,0 +1,128 @@ +# 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 sys + +from mox3 import mox + +from neutronclient.common import exceptions +from neutronclient.neutron import v2_0 as neutronV2_0 +from neutronclient.neutron.v2_0 import network +from neutronclient.neutron.v2_0 import tag +from neutronclient import shell +from neutronclient.tests.unit import test_cli20 + + +class CLITestV20Tag(test_cli20.CLITestV20Base): + def _test_tag_operation(self, cmd, path, method, args, prog_name, + body=None): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + if body: + body = test_cli20.MyComparator(body, self.client) + self.client.httpclient.request( + test_cli20.MyUrlComparator( + test_cli20.end_url(path, format=self.format), self.client), + method, body=body, + headers=mox.ContainsKeyValue( + 'X-Auth-Token', test_cli20.TOKEN)).AndReturn( + (test_cli20.MyResp(204), None)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser(prog_name) + shell.run_command(cmd, cmd_parser, args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + + def _test_tags_query(self, cmd, resources, args, query): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + path = getattr(self.client, resources + "_path") + res = {resources: [{'id': 'myid'}]} + resstr = self.client.serialize(res) + self.client.httpclient.request( + test_cli20.MyUrlComparator( + test_cli20.end_url(path, query, format=self.format), + self.client), + 'GET', body=None, + headers=mox.ContainsKeyValue( + 'X-Auth-Token', test_cli20.TOKEN)).AndReturn( + (test_cli20.MyResp(200), resstr)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("list_networks") + shell.run_command(cmd, cmd_parser, args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + self.assertIn('myid', _str) + + def _make_tag_path(self, resource, resource_id, tag): + path = getattr(self.client, "tag_path") + resource_plural = neutronV2_0._get_resource_plural(resource, + self.client) + return path % (resource_plural, resource_id, tag) + + def _make_tags_path(self, resource, resource_id): + path = getattr(self.client, "tags_path") + resource_plural = neutronV2_0._get_resource_plural(resource, + self.client) + return path % (resource_plural, resource_id) + + def test_add_tag(self): + cmd = tag.AddTag(test_cli20.MyApp(sys.stdout), None) + path = self._make_tag_path('network', 'myid', 'red') + args = ['--resource-type', 'network', '--resource', 'myid', + '--tag', 'red'] + self._test_tag_operation(cmd, path, 'PUT', args, "tag-add") + + def test_replace_tag(self): + cmd = tag.ReplaceTag(test_cli20.MyApp(sys.stdout), None) + path = self._make_tags_path('network', 'myid') + args = ['--resource-type', 'network', '--resource', 'myid', + '--tag', 'red', '--tag', 'blue'] + body = {'tags': ['red', 'blue']} + self._test_tag_operation(cmd, path, 'PUT', args, "tag-replace", + body=body) + + def test_remove_tag(self): + cmd = tag.RemoveTag(test_cli20.MyApp(sys.stdout), None) + path = self._make_tag_path('network', 'myid', 'red') + args = ['--resource-type', 'network', '--resource', 'myid', + '--tag', 'red'] + self._test_tag_operation(cmd, path, 'DELETE', args, "tag-remove") + + def test_remove_tag_all(self): + cmd = tag.RemoveTag(test_cli20.MyApp(sys.stdout), None) + path = self._make_tags_path('network', 'myid') + args = ['--resource-type', 'network', '--resource', 'myid', + '--all'] + self._test_tag_operation(cmd, path, 'DELETE', args, "tag-remove") + + def test_no_tag_nor_all(self): + cmd = tag.RemoveTag(test_cli20.MyApp(sys.stdout), None) + path = self._make_tags_path('network', 'myid') + args = ['--resource-type', 'network', '--resource', 'myid'] + self.assertRaises(exceptions.CommandError, self._test_tag_operation, + cmd, path, 'DELETE', args, "tag-remove") + + def test_tags_query(self): + # This test examines that '-' in the tag related filters + # is not converted to '_'. + resources = 'networks' + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) + self.mox.StubOutWithMock(network.ListNetwork, "extend_list") + network.ListNetwork.extend_list(mox.IsA(list), mox.IgnoreArg()) + args = ['--not-tags', 'red,blue', '--tags-any', 'green', + '--not-tags-any', 'black'] + query = "not-tags=red,blue&tags-any=green¬-tags-any=black" + self._test_tags_query(cmd, resources, args, query) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index dbe3af5..f25b007 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -528,6 +528,8 @@ class Client(ClientBase): bgp_peer_path = "/bgp-peers/%s" network_ip_availabilities_path = '/network-ip-availabilities' network_ip_availability_path = '/network-ip-availabilities/%s' + tags_path = "/%s/%s/tags" + tag_path = "/%s/%s/tags/%s" # API has no way to report plurals, so we have to hard code them EXTED_PLURALS = {'routers': 'router', @@ -2002,6 +2004,26 @@ class Client(ClientBase): return self.get(self.network_ip_availability_path % (network), params=_params) + @APIParamsCall + def add_tag(self, resource_type, resource_id, tag, **_params): + """Add a tag on the resource.""" + return self.put(self.tag_path % (resource_type, resource_id, tag)) + + @APIParamsCall + def replace_tag(self, resource_type, resource_id, body, **_params): + """Replace tags on the resource.""" + return self.put(self.tags_path % (resource_type, resource_id), body) + + @APIParamsCall + def remove_tag(self, resource_type, resource_id, tag, **_params): + """Remove a tag on the resource.""" + return self.delete(self.tag_path % (resource_type, resource_id, tag)) + + @APIParamsCall + def remove_tag_all(self, resource_type, resource_id, **_params): + """Remove all tags on the resource.""" + return self.delete(self.tags_path % (resource_type, resource_id)) + def __init__(self, **kwargs): """Initialize a new client for the Neutron v2.0 API.""" super(Client, self).__init__(**kwargs) diff --git a/releasenotes/notes/add-tag-support-bad62d60ecc7075c.yaml b/releasenotes/notes/add-tag-support-bad62d60ecc7075c.yaml new file mode 100644 index 0000000..cc5b6ee --- /dev/null +++ b/releasenotes/notes/add-tag-support-bad62d60ecc7075c.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + CLI support for tag. + + * The ``tag-add`` command sets a tag on the network resource. It also + includes ``--resource-type``, ``--resource`` and ``--tag`` options. + * The ``tag-replace`` command replaces tags on the network resource. It + also includes ``--resource-type``, ``--resource`` and ``--tag`` + options. More than one ``--tag`` options can be set. + * The ``tag-remove`` command removes tags on the network resource. It also + includes ``--resource-type``, ``--resource``, ``--tag`` and ``--all`` + options. The ``--all`` option allow to remove all tags on the network + resource. + * The ``net-list`` command includes ``--tags``, ``--tags-any``, + ``--not-tags`` and ``--not-tags-any`` options. \ No newline at end of file From 6f2963d75204acd515089b70496f2ca3d0397f60 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Fri, 8 Jan 2016 23:16:43 +0900 Subject: [PATCH 51/57] refactor: Avoid overriding run() in cliff command cliff Command subclasses are suggested to override take_action. run() method is reserved for extending base classes. Thus, this commit changes to implement take_action() instead of run(). Closes-Bug: #1532258 Change-Id: Id845a80dbad2f436df5a55c9be502068e6f7b291 --- neutronclient/common/extension.py | 8 ++++---- neutronclient/neutron/v2_0/__init__.py | 10 ++-------- neutronclient/neutron/v2_0/agentscheduler.py | 8 ++++---- neutronclient/neutron/v2_0/bgp/speaker.py | 8 ++++---- neutronclient/neutron/v2_0/flavor/flavor.py | 4 ++-- neutronclient/neutron/v2_0/floatingip.py | 4 ++-- neutronclient/neutron/v2_0/fw/firewallpolicy.py | 4 ++-- neutronclient/neutron/v2_0/lb/healthmonitor.py | 4 ++-- neutronclient/neutron/v2_0/nsx/networkgateway.py | 4 ++-- neutronclient/neutron/v2_0/purge.py | 2 +- neutronclient/neutron/v2_0/quota.py | 2 +- neutronclient/neutron/v2_0/router.py | 6 +++--- 12 files changed, 29 insertions(+), 35 deletions(-) diff --git a/neutronclient/common/extension.py b/neutronclient/common/extension.py index f7e361b..90f44a7 100644 --- a/neutronclient/common/extension.py +++ b/neutronclient/common/extension.py @@ -54,14 +54,14 @@ class ClientExtensionList(NeutronClientExtension, neutronV20.ListCommand): class ClientExtensionDelete(NeutronClientExtension, neutronV20.DeleteCommand): - def run(self, parsed_args): + def take_action(self, parsed_args): # NOTE(mdietz): Calls 'execute' to provide a consistent pattern # for any implementers adding extensions with # regard to any other extension verb. return self.execute(parsed_args) def execute(self, parsed_args): - return super(ClientExtensionDelete, self).run(parsed_args) + return super(ClientExtensionDelete, self).take_action(parsed_args) class ClientExtensionCreate(NeutronClientExtension, neutronV20.CreateCommand): @@ -76,11 +76,11 @@ class ClientExtensionCreate(NeutronClientExtension, neutronV20.CreateCommand): class ClientExtensionUpdate(NeutronClientExtension, neutronV20.UpdateCommand): - def run(self, parsed_args): + def take_action(self, parsed_args): # NOTE(mdietz): Calls 'execute' to provide a consistent pattern # for any implementers adding extensions with # regard to any other extension verb. return self.execute(parsed_args) def execute(self, parsed_args): - return super(ClientExtensionUpdate, self).run(parsed_args) + return super(ClientExtensionUpdate, self).take_action(parsed_args) diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index d740aac..14a3548 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -397,12 +397,6 @@ class NeutronCommand(command.Command): shadow_resource = None parent_id = None - # TODO(amotoki): Remove take_action here. It should be an abstract method - # as cliff.command.Command does. To do this, we need to avoid overriding - # run() directly. - def take_action(self, parsed_args): - return self.get_data(parsed_args) - @property def cmd_resource(self): if self.shadow_resource: @@ -517,7 +511,7 @@ class UpdateCommand(NeutronCommand): self.add_known_arguments(parser) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)', parsed_args) self.set_extra_attrs(parsed_args) neutron_client = self.get_client() @@ -574,7 +568,7 @@ class DeleteCommand(NeutronCommand): self.add_known_arguments(parser) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)', parsed_args) self.set_extra_attrs(parsed_args) neutron_client = self.get_client() diff --git a/neutronclient/neutron/v2_0/agentscheduler.py b/neutronclient/neutron/v2_0/agentscheduler.py index 24287b7..dacfab8 100644 --- a/neutronclient/neutron/v2_0/agentscheduler.py +++ b/neutronclient/neutron/v2_0/agentscheduler.py @@ -40,7 +40,7 @@ class AddNetworkToDhcpAgent(neutronV20.NeutronCommand): help=_('Network to add.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() _net_id = neutronV20.find_resourceid_by_name_or_id( @@ -66,7 +66,7 @@ class RemoveNetworkFromDhcpAgent(neutronV20.NeutronCommand): help=_('Network to remove.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() _net_id = neutronV20.find_resourceid_by_name_or_id( @@ -142,7 +142,7 @@ class AddRouterToL3Agent(neutronV20.NeutronCommand): help=_('Router to add.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() _id = neutronV20.find_resourceid_by_name_or_id( @@ -168,7 +168,7 @@ class RemoveRouterFromL3Agent(neutronV20.NeutronCommand): help=_('Router to remove.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() _id = neutronV20.find_resourceid_by_name_or_id( diff --git a/neutronclient/neutron/v2_0/bgp/speaker.py b/neutronclient/neutron/v2_0/bgp/speaker.py index f2a3df7..1a70734 100755 --- a/neutronclient/neutron/v2_0/bgp/speaker.py +++ b/neutronclient/neutron/v2_0/bgp/speaker.py @@ -153,7 +153,7 @@ class AddPeerToSpeaker(neutronv20.NeutronCommand): help=_('ID or name of the BGP peer to add.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): neutron_client = self.get_client() _speaker_id = get_bgp_speaker_id(neutron_client, parsed_args.bgp_speaker) @@ -182,7 +182,7 @@ class RemovePeerFromSpeaker(neutronv20.NeutronCommand): help=_('ID or name of the BGP peer to remove.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): neutron_client = self.get_client() _speaker_id = get_bgp_speaker_id(neutron_client, parsed_args.bgp_speaker) @@ -211,7 +211,7 @@ class AddNetworkToSpeaker(neutronv20.NeutronCommand): help=_('ID or name of the network to add.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): neutron_client = self.get_client() _speaker_id = get_bgp_speaker_id(neutron_client, parsed_args.bgp_speaker) @@ -239,7 +239,7 @@ class RemoveNetworkFromSpeaker(neutronv20.NeutronCommand): help=_('ID or name of the network to remove.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): neutron_client = self.get_client() _speaker_id = get_bgp_speaker_id(neutron_client, parsed_args.bgp_speaker) diff --git a/neutronclient/neutron/v2_0/flavor/flavor.py b/neutronclient/neutron/v2_0/flavor/flavor.py index 30e3ae4..57ec8fa 100644 --- a/neutronclient/neutron/v2_0/flavor/flavor.py +++ b/neutronclient/neutron/v2_0/flavor/flavor.py @@ -118,7 +118,7 @@ class AssociateFlavor(neutronV20.NeutronCommand): 'flavor.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): neutron_client = self.get_client() flavor_id = neutronV20.find_resourceid_by_name_or_id( neutron_client, 'flavor', parsed_args.flavor) @@ -151,7 +151,7 @@ class DisassociateFlavor(neutronV20.NeutronCommand): 'flavor.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): neutron_client = self.get_client() flavor_id = neutronV20.find_resourceid_by_name_or_id( neutron_client, 'flavor', parsed_args.flavor) diff --git a/neutronclient/neutron/v2_0/floatingip.py b/neutronclient/neutron/v2_0/floatingip.py index e361a29..f208896 100644 --- a/neutronclient/neutron/v2_0/floatingip.py +++ b/neutronclient/neutron/v2_0/floatingip.py @@ -115,7 +115,7 @@ class AssociateFloatingIP(neutronV20.NeutronCommand): help=argparse.SUPPRESS) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() update_dict = {} @@ -139,7 +139,7 @@ class DisassociateFloatingIP(neutronV20.NeutronCommand): help=_('ID of the floating IP to disassociate.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() neutron_client.update_floatingip(parsed_args.floatingip_id, diff --git a/neutronclient/neutron/v2_0/fw/firewallpolicy.py b/neutronclient/neutron/v2_0/fw/firewallpolicy.py index 08d623a..a01fc72 100644 --- a/neutronclient/neutron/v2_0/fw/firewallpolicy.py +++ b/neutronclient/neutron/v2_0/fw/firewallpolicy.py @@ -179,7 +179,7 @@ class FirewallPolicyInsertRule(neutronv20.UpdateCommand): self.add_known_arguments(parser) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): neutron_client = self.get_client() body = self.args2body(parsed_args) _id = neutronv20.find_resourceid_by_name_or_id(neutron_client, @@ -217,7 +217,7 @@ class FirewallPolicyRemoveRule(neutronv20.UpdateCommand): self.add_known_arguments(parser) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): neutron_client = self.get_client() body = self.args2body(parsed_args) _id = neutronv20.find_resourceid_by_name_or_id(neutron_client, diff --git a/neutronclient/neutron/v2_0/lb/healthmonitor.py b/neutronclient/neutron/v2_0/lb/healthmonitor.py index ee5d70b..6fea0a1 100644 --- a/neutronclient/neutron/v2_0/lb/healthmonitor.py +++ b/neutronclient/neutron/v2_0/lb/healthmonitor.py @@ -124,7 +124,7 @@ class AssociateHealthMonitor(neutronV20.NeutronCommand): help=_('ID of the pool to be associated with the health monitor.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): neutron_client = self.get_client() body = {'health_monitor': {'id': parsed_args.health_monitor_id}} pool_id = neutronV20.find_resourceid_by_name_or_id( @@ -150,7 +150,7 @@ class DisassociateHealthMonitor(neutronV20.NeutronCommand): help=_('ID of the pool to be associated with the health monitor.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): neutron_client = self.get_client() pool_id = neutronV20.find_resourceid_by_name_or_id( neutron_client, 'pool', parsed_args.pool_id) diff --git a/neutronclient/neutron/v2_0/nsx/networkgateway.py b/neutronclient/neutron/v2_0/nsx/networkgateway.py index 46d83e9..c7df513 100644 --- a/neutronclient/neutron/v2_0/nsx/networkgateway.py +++ b/neutronclient/neutron/v2_0/nsx/networkgateway.py @@ -234,7 +234,7 @@ class NetworkGatewayInterfaceCommand(neutronV20.NeutronCommand): class ConnectNetworkGateway(NetworkGatewayInterfaceCommand): """Add an internal network interface to a router.""" - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() (gateway_id, network_id) = self.retrieve_ids(neutron_client, @@ -252,7 +252,7 @@ class ConnectNetworkGateway(NetworkGatewayInterfaceCommand): class DisconnectNetworkGateway(NetworkGatewayInterfaceCommand): """Remove a network from a network gateway.""" - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() (gateway_id, network_id) = self.retrieve_ids(neutron_client, diff --git a/neutronclient/neutron/v2_0/purge.py b/neutronclient/neutron/v2_0/purge.py index 6d8e9d9..6176296 100644 --- a/neutronclient/neutron/v2_0/purge.py +++ b/neutronclient/neutron/v2_0/purge.py @@ -125,7 +125,7 @@ class Purge(neutronV20.NeutronCommand): help=_('ID of Tenant owning the resources to be deleted.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): neutron_client = self.get_client() self.any_failures = False diff --git a/neutronclient/neutron/v2_0/quota.py b/neutronclient/neutron/v2_0/quota.py index a88f0c6..985e619 100644 --- a/neutronclient/neutron/v2_0/quota.py +++ b/neutronclient/neutron/v2_0/quota.py @@ -52,7 +52,7 @@ class DeleteQuota(neutronV20.NeutronCommand): help=argparse.SUPPRESS, nargs='?') return parser - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() tenant_id = get_tenant_id(parsed_args, neutron_client) diff --git a/neutronclient/neutron/v2_0/router.py b/neutronclient/neutron/v2_0/router.py index c372f98..76022e7 100644 --- a/neutronclient/neutron/v2_0/router.py +++ b/neutronclient/neutron/v2_0/router.py @@ -161,7 +161,7 @@ class RouterInterfaceCommand(neutronV20.NeutronCommand): 'subnet.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() @@ -236,7 +236,7 @@ class SetGatewayRouter(neutronV20.NeutronCommand): 'You can repeat this option.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() _router_id = neutronV20.find_resourceid_by_name_or_id( @@ -273,7 +273,7 @@ class RemoveGatewayRouter(neutronV20.NeutronCommand): help=_('ID or name of the router.')) return parser - def run(self, parsed_args): + def take_action(self, parsed_args): self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() _router_id = neutronV20.find_resourceid_by_name_or_id( From 724d4b5c1145a39d2faf765f608d7a6a06bfbe4f Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Fri, 8 Jan 2016 23:27:55 +0900 Subject: [PATCH 52/57] refactor: Merge all debug logging at the beginning of take_action After refactoring made in this series of patches, all neutronclient commands implements take_action(). Debug logging at the beginning of take_action can be implemented in a signle place. Change-Id: Ic766c0bd0f203b2408dc459247804c7d9290b0d5 --- neutronclient/neutron/v2_0/__init__.py | 9 ++++----- neutronclient/neutron/v2_0/agentscheduler.py | 4 ---- neutronclient/neutron/v2_0/floatingip.py | 2 -- neutronclient/neutron/v2_0/lb/pool.py | 1 - neutronclient/neutron/v2_0/lb/v2/loadbalancer.py | 1 - neutronclient/neutron/v2_0/nsx/networkgateway.py | 2 -- neutronclient/neutron/v2_0/quota.py | 4 ---- neutronclient/neutron/v2_0/router.py | 3 --- 8 files changed, 4 insertions(+), 22 deletions(-) diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index 14a3548..528f3af 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -397,6 +397,10 @@ class NeutronCommand(command.Command): shadow_resource = None parent_id = None + def run(self, parsed_args): + self.log.debug('run(%s)', parsed_args) + return super(NeutronCommand, self).run(parsed_args) + @property def cmd_resource(self): if self.shadow_resource: @@ -466,7 +470,6 @@ class CreateCommand(NeutronCommand, show.ShowOne): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) self.set_extra_attrs(parsed_args) neutron_client = self.get_client() _extra_values = parse_args_to_dict(self.values_specs) @@ -512,7 +515,6 @@ class UpdateCommand(NeutronCommand): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)', parsed_args) self.set_extra_attrs(parsed_args) neutron_client = self.get_client() _extra_values = parse_args_to_dict(self.values_specs) @@ -569,7 +571,6 @@ class DeleteCommand(NeutronCommand): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)', parsed_args) self.set_extra_attrs(parsed_args) neutron_client = self.get_client() obj_deleter = getattr(neutron_client, @@ -758,7 +759,6 @@ class ListCommand(NeutronCommand, lister.Lister): for s in info), ) def take_action(self, parsed_args): - self.log.debug('run(%s)', parsed_args) self.set_extra_attrs(parsed_args) data = self.retrieve_list(parsed_args) self.extend_list(data, parsed_args) @@ -788,7 +788,6 @@ class ShowCommand(NeutronCommand, show.ShowOne): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)', parsed_args) self.set_extra_attrs(parsed_args) neutron_client = self.get_client() diff --git a/neutronclient/neutron/v2_0/agentscheduler.py b/neutronclient/neutron/v2_0/agentscheduler.py index dacfab8..2d2b1f7 100644 --- a/neutronclient/neutron/v2_0/agentscheduler.py +++ b/neutronclient/neutron/v2_0/agentscheduler.py @@ -41,7 +41,6 @@ class AddNetworkToDhcpAgent(neutronV20.NeutronCommand): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() _net_id = neutronV20.find_resourceid_by_name_or_id( neutron_client, 'network', parsed_args.network) @@ -67,7 +66,6 @@ class RemoveNetworkFromDhcpAgent(neutronV20.NeutronCommand): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() _net_id = neutronV20.find_resourceid_by_name_or_id( neutron_client, 'network', parsed_args.network) @@ -143,7 +141,6 @@ class AddRouterToL3Agent(neutronV20.NeutronCommand): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() _id = neutronV20.find_resourceid_by_name_or_id( neutron_client, 'router', parsed_args.router) @@ -169,7 +166,6 @@ class RemoveRouterFromL3Agent(neutronV20.NeutronCommand): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() _id = neutronV20.find_resourceid_by_name_or_id( neutron_client, 'router', parsed_args.router) diff --git a/neutronclient/neutron/v2_0/floatingip.py b/neutronclient/neutron/v2_0/floatingip.py index f208896..78c0dd3 100644 --- a/neutronclient/neutron/v2_0/floatingip.py +++ b/neutronclient/neutron/v2_0/floatingip.py @@ -116,7 +116,6 @@ class AssociateFloatingIP(neutronV20.NeutronCommand): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() update_dict = {} neutronV20.update_dict(parsed_args, update_dict, @@ -140,7 +139,6 @@ class DisassociateFloatingIP(neutronV20.NeutronCommand): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() neutron_client.update_floatingip(parsed_args.floatingip_id, {'floatingip': {'port_id': None}}) diff --git a/neutronclient/neutron/v2_0/lb/pool.py b/neutronclient/neutron/v2_0/lb/pool.py index 86497a7..5d3a9db 100644 --- a/neutronclient/neutron/v2_0/lb/pool.py +++ b/neutronclient/neutron/v2_0/lb/pool.py @@ -108,7 +108,6 @@ class RetrievePoolStats(neutronV20.ShowCommand): resource = 'pool' def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() pool_id = neutronV20.find_resourceid_by_name_or_id( self.get_client(), 'pool', parsed_args.id) diff --git a/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py b/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py index 5a60eb4..ceba499 100644 --- a/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py +++ b/neutronclient/neutron/v2_0/lb/v2/loadbalancer.py @@ -102,7 +102,6 @@ class RetrieveLoadBalancerStats(neutronV20.ShowCommand): resource = 'loadbalancer' def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() neutron_client.format = parsed_args.request_format loadbalancer_id = neutronV20.find_resourceid_by_name_or_id( diff --git a/neutronclient/neutron/v2_0/nsx/networkgateway.py b/neutronclient/neutron/v2_0/nsx/networkgateway.py index c7df513..153c855 100644 --- a/neutronclient/neutron/v2_0/nsx/networkgateway.py +++ b/neutronclient/neutron/v2_0/nsx/networkgateway.py @@ -235,7 +235,6 @@ class ConnectNetworkGateway(NetworkGatewayInterfaceCommand): """Add an internal network interface to a router.""" def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() (gateway_id, network_id) = self.retrieve_ids(neutron_client, parsed_args) @@ -253,7 +252,6 @@ class DisconnectNetworkGateway(NetworkGatewayInterfaceCommand): """Remove a network from a network gateway.""" def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() (gateway_id, network_id) = self.retrieve_ids(neutron_client, parsed_args) diff --git a/neutronclient/neutron/v2_0/quota.py b/neutronclient/neutron/v2_0/quota.py index 985e619..e7e74b1 100644 --- a/neutronclient/neutron/v2_0/quota.py +++ b/neutronclient/neutron/v2_0/quota.py @@ -53,7 +53,6 @@ class DeleteQuota(neutronV20.NeutronCommand): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() tenant_id = get_tenant_id(parsed_args, neutron_client) obj_deleter = getattr(neutron_client, @@ -76,7 +75,6 @@ class ListQuota(neutronV20.NeutronCommand, lister.Lister): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)', parsed_args) neutron_client = self.get_client() search_opts = {} self.log.debug('search options: %s', search_opts) @@ -115,7 +113,6 @@ class ShowQuota(neutronV20.NeutronCommand, show.ShowOne): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)', parsed_args) neutron_client = self.get_client() tenant_id = get_tenant_id(parsed_args, neutron_client) params = {} @@ -214,7 +211,6 @@ class UpdateQuota(neutronV20.NeutronCommand, show.ShowOne): return {self.resource: quota} def take_action(self, parsed_args): - self.log.debug('run(%s)', parsed_args) neutron_client = self.get_client() _extra_values = neutronV20.parse_args_to_dict(self.values_specs) neutronV20._merge_args(self, parsed_args, _extra_values, diff --git a/neutronclient/neutron/v2_0/router.py b/neutronclient/neutron/v2_0/router.py index 76022e7..5eba1f2 100644 --- a/neutronclient/neutron/v2_0/router.py +++ b/neutronclient/neutron/v2_0/router.py @@ -162,7 +162,6 @@ class RouterInterfaceCommand(neutronV20.NeutronCommand): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() if '=' in parsed_args.interface: @@ -237,7 +236,6 @@ class SetGatewayRouter(neutronV20.NeutronCommand): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() _router_id = neutronV20.find_resourceid_by_name_or_id( neutron_client, self.resource, parsed_args.router) @@ -274,7 +272,6 @@ class RemoveGatewayRouter(neutronV20.NeutronCommand): return parser def take_action(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) neutron_client = self.get_client() _router_id = neutronV20.find_resourceid_by_name_or_id( neutron_client, self.resource, parsed_args.router) From 0740766467a3c8de0bf9d03689158a86591bd506 Mon Sep 17 00:00:00 2001 From: Jaspinder Date: Mon, 4 Jan 2016 05:08:41 -0600 Subject: [PATCH 53/57] fix: can't get authentication with os-token and os-url Currently, there is no way to authenticate a user through Neutron CLI by just using endpoint and token authentication. This simple fix will at least allow for that to be permitted. Change-Id: Ia7d285af224ef225aa20f83d7d4c87b81aac58ed Closes-Bug: 1450414 --- neutronclient/shell.py | 21 +++++++++++++++---- ...ndpoint-auth-support-26bf7ee12e4ec833.yaml | 7 +++++++ 2 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/add-token-endpoint-auth-support-26bf7ee12e4ec833.yaml diff --git a/neutronclient/shell.py b/neutronclient/shell.py index 5261a34..64de864 100644 --- a/neutronclient/shell.py +++ b/neutronclient/shell.py @@ -898,11 +898,22 @@ class NeutronShell(app.App): cloud=self.options.os_cloud, argparse=self.options, network_api_version=self.api_version) verify, cert = cloud_config.get_requests_verify_args() - auth = cloud_config.get_auth() - auth_session = session.Session( - auth=auth, verify=verify, cert=cert, - timeout=self.options.http_timeout) + # TODO(singhj): Remove dependancy on HTTPClient + # for the case of token-endpoint authentication + + # When using token-endpoint authentication legacy + # HTTPClient will be used, otherwise SessionClient + # will be used. + if self.options.os_token and self.options.os_url: + auth = None + auth_session = None + else: + auth = cloud_config.get_auth() + + auth_session = session.Session( + auth=auth, verify=verify, cert=cert, + timeout=self.options.http_timeout) interface = self.options.os_endpoint_type or self.endpoint_type if interface.endswith('URL'): @@ -911,6 +922,8 @@ class NeutronShell(app.App): retries=self.options.retries, raise_errors=False, session=auth_session, + url=self.options.os_url, + token=self.options.os_token, region_name=cloud_config.get_region_name(), api_version=cloud_config.get_api_version('network'), service_type=cloud_config.get_service_type('network'), diff --git a/releasenotes/notes/add-token-endpoint-auth-support-26bf7ee12e4ec833.yaml b/releasenotes/notes/add-token-endpoint-auth-support-26bf7ee12e4ec833.yaml new file mode 100644 index 0000000..d3445cd --- /dev/null +++ b/releasenotes/notes/add-token-endpoint-auth-support-26bf7ee12e4ec833.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + CLI support for token-endpoint authentication. + + * Allows for authentication via ``--os-token`` and ``--os-url`` options + or the ``OS_TOKEN`` and ``OS_URL`` environment variables, respectively From d3f13f4c20eba725fa8228a25cdfd77d47b7569d Mon Sep 17 00:00:00 2001 From: Henry Gessau Date: Wed, 24 Feb 2016 12:46:58 -0500 Subject: [PATCH 54/57] Support dry-run option for auto-allocated-topology Add the ability to pass fields with the auto-allocated-topology-show command to support the dry-run validation option of the API. With dry-run the CLI result is "Pass" or the error message from the response. Rename the client binding from show_ to get_. Also provide a client binding for validating the auto-allocation requirements. Partially-Implements: blueprint get-me-a-network DocImpact: Add info about dry-run to the auto-allocate section in the networking guide. Change-Id: Ieba6f3cde23a8a93067b8239b096d5103f6a3128 --- .../neutron/v2_0/auto_allocated_topology.py | 18 +++++++++++++++++- .../tests/unit/test_auto_allocated_topology.py | 14 ++++++++++++++ neutronclient/v2_0/client.py | 6 +++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/neutronclient/neutron/v2_0/auto_allocated_topology.py b/neutronclient/neutron/v2_0/auto_allocated_topology.py index ad96fd9..3f0f58b 100755 --- a/neutronclient/neutron/v2_0/auto_allocated_topology.py +++ b/neutronclient/neutron/v2_0/auto_allocated_topology.py @@ -19,6 +19,7 @@ from cliff import show from oslo_serialization import jsonutils from neutronclient._i18n import _ +from neutronclient.common import exceptions from neutronclient.neutron import v2_0 @@ -29,6 +30,11 @@ class ShowAutoAllocatedTopology(v2_0.NeutronCommand, show.ShowOne): def get_parser(self, prog_name): parser = super(ShowAutoAllocatedTopology, self).get_parser(prog_name) + parser.add_argument( + '--dry-run', + help=_('Validate the requirements for auto-allocated-topology. ' + '(Does not return a topology.)'), + action='store_true') parser.add_argument( '--tenant-id', metavar='tenant-id', help=_('The owner tenant ID.')) @@ -44,8 +50,16 @@ class ShowAutoAllocatedTopology(v2_0.NeutronCommand, show.ShowOne): def take_action(self, parsed_args): client = self.get_client() + extra_values = v2_0.parse_args_to_dict(self.values_specs) + if extra_values: + raise exceptions.CommandError( + _("Invalid argument(s): --%s") % ', --'.join(extra_values)) tenant_id = parsed_args.tenant_id or parsed_args.pos_tenant_id - data = client.show_auto_allocated_topology(tenant_id) + if parsed_args.dry_run: + data = client.validate_auto_allocated_topology_requirements( + tenant_id) + else: + data = client.get_auto_allocated_topology(tenant_id) if self.resource in data: for k, v in data[self.resource].items(): if isinstance(v, list): @@ -58,6 +72,8 @@ class ShowAutoAllocatedTopology(v2_0.NeutronCommand, show.ShowOne): else: value += str(_item) data[self.resource][k] = value + elif v == "dry-run=pass": + return ("dry-run",), ("pass",) elif v is None: data[self.resource][k] = '' return zip(*sorted(data[self.resource].items())) diff --git a/neutronclient/tests/unit/test_auto_allocated_topology.py b/neutronclient/tests/unit/test_auto_allocated_topology.py index b0f8cf0..af2742b 100755 --- a/neutronclient/tests/unit/test_auto_allocated_topology.py +++ b/neutronclient/tests/unit/test_auto_allocated_topology.py @@ -39,3 +39,17 @@ class TestAutoAllocatedTopologyJSON(test_cli20.CLITestV20Base): cmd = aat.ShowAutoAllocatedTopology(test_cli20.MyApp(sys.stdout), None) args = [] self._test_show_resource(resource, cmd, "None", args) + + def test_show_auto_allocated_topology_dry_run_as_tenant(self): + resource = 'auto_allocated_topology' + cmd = aat.ShowAutoAllocatedTopology(test_cli20.MyApp(sys.stdout), None) + args = ['--dry-run'] + self._test_show_resource(resource, cmd, "None", args, + fields=('dry-run',)) + + def test_show_auto_allocated_topology_dry_run_as_admin(self): + resource = 'auto_allocated_topology' + cmd = aat.ShowAutoAllocatedTopology(test_cli20.MyApp(sys.stdout), None) + args = ['--dry-run', 'some-tenant'] + self._test_show_resource(resource, cmd, "some-tenant", args, + fields=('dry-run',)) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index f25b007..71bd503 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -1902,12 +1902,16 @@ class Client(ClientBase): retrieve_all, **_params) @APIParamsCall - def show_auto_allocated_topology(self, tenant_id, **_params): + def get_auto_allocated_topology(self, tenant_id, **_params): """Fetch information about a tenant's auto-allocated topology.""" return self.get( self.auto_allocated_topology_path % tenant_id, params=_params) + def validate_auto_allocated_topology_requirements(self, tenant_id): + """Validate requirements for getting an auto-allocated topology.""" + return self.get_auto_allocated_topology(tenant_id, fields=['dry-run']) + @APIParamsCall def list_bgp_speakers(self, retrieve_all=True, **_params): """Fetches a list of all BGP speakers for a tenant.""" From 32088264e5e65a56ca320d855ee4ee1624b2079f Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Thu, 3 Mar 2016 08:44:02 +0900 Subject: [PATCH 55/57] Update relnote on fix of bug 1450414 https://review.openstack.org/#/c/263337/ is actually a bug fix rather than a new feature. Let's update the release note. Change-Id: I8b912abd95bb9c24edfc95dec26e2cfa7cf547e0 --- .../add-token-endpoint-auth-support-26bf7ee12e4ec833.yaml | 7 ------- .../fix-token-endpoint-auth-support-26bf7ee12e4ec833.yaml | 6 ++++++ 2 files changed, 6 insertions(+), 7 deletions(-) delete mode 100644 releasenotes/notes/add-token-endpoint-auth-support-26bf7ee12e4ec833.yaml create mode 100644 releasenotes/notes/fix-token-endpoint-auth-support-26bf7ee12e4ec833.yaml diff --git a/releasenotes/notes/add-token-endpoint-auth-support-26bf7ee12e4ec833.yaml b/releasenotes/notes/add-token-endpoint-auth-support-26bf7ee12e4ec833.yaml deleted file mode 100644 index d3445cd..0000000 --- a/releasenotes/notes/add-token-endpoint-auth-support-26bf7ee12e4ec833.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -features: - - | - CLI support for token-endpoint authentication. - - * Allows for authentication via ``--os-token`` and ``--os-url`` options - or the ``OS_TOKEN`` and ``OS_URL`` environment variables, respectively diff --git a/releasenotes/notes/fix-token-endpoint-auth-support-26bf7ee12e4ec833.yaml b/releasenotes/notes/fix-token-endpoint-auth-support-26bf7ee12e4ec833.yaml new file mode 100644 index 0000000..9f8b290 --- /dev/null +++ b/releasenotes/notes/fix-token-endpoint-auth-support-26bf7ee12e4ec833.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - Fix `bug 1450414 `_ + that authentication with via ``--os-token`` and ``--os-url`` options + (or corresponding environment variables) does not work + after keystone v3 API support. From 3d1ea8bb16b243871a739b1004bb1663855fe3c3 Mon Sep 17 00:00:00 2001 From: Rabi Mishra Date: Fri, 4 Mar 2016 13:48:15 +0530 Subject: [PATCH 56/57] Fix TypeError with error message neutronclient throws type error when dealing with i18n messages. This patch fixes it. Change-Id: I08190eb7f6c54f0d44c671b1806423bba22ac0bf Closes-Bug: #1552760 --- neutronclient/common/exceptions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neutronclient/common/exceptions.py b/neutronclient/common/exceptions.py index 6aff6d6..2e4d679 100644 --- a/neutronclient/common/exceptions.py +++ b/neutronclient/common/exceptions.py @@ -70,7 +70,8 @@ class NeutronClientException(NeutronException): if self.request_ids: req_ids_msg = self.req_ids_msg % self.request_ids if message: - message += '\n' + req_ids_msg + message = _('%(msg)s\n%(id)s') % {'msg': message, + 'id': req_ids_msg} else: message = req_ids_msg super(NeutronClientException, self).__init__(message, **kwargs) From 8fe5386a006992bdf606872d5e8ad57e60786c50 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Fri, 4 Mar 2016 19:39:53 +0900 Subject: [PATCH 57/57] Add release note of critial TypeError fix This is a follow-up patch of https://review.openstack.org/#/c/288301 Related-Bug: #1552760 Change-Id: Ie5baf6df82db6b5d25bd4125332d8c8edfec9797 --- .../fix-exception-typeerror-4.1.0-b37d738146575ed5.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 releasenotes/notes/fix-exception-typeerror-4.1.0-b37d738146575ed5.yaml diff --git a/releasenotes/notes/fix-exception-typeerror-4.1.0-b37d738146575ed5.yaml b/releasenotes/notes/fix-exception-typeerror-4.1.0-b37d738146575ed5.yaml new file mode 100644 index 0000000..a21eebb --- /dev/null +++ b/releasenotes/notes/fix-exception-typeerror-4.1.0-b37d738146575ed5.yaml @@ -0,0 +1,5 @@ +--- +critical: + - Fix a critical bug that when lazy translation is enabled + NeutronClientException raises a TypeError + (`bug 1552760 `_).