summaryrefslogtreecommitdiff
path: root/neutronclient
diff options
context:
space:
mode:
authorMark McClain <mark.mcclain@dreamhost.com>2013-07-02 18:44:42 -0400
committerMark McClain <mark.mcclain@dreamhost.com>2013-07-03 11:56:44 -0400
commit93ac15bfeb51ee6a097d878e4eeef69cd2b3a6bc (patch)
tree39acb2ba93a28e1f03a3e6d739845758cd58715b /neutronclient
parent8ed38707b12ae6e77480ae8d8542712d63b7fc70 (diff)
downloadpython-neutronclient-2.2.4.tar.gz
Rename quantumclient to neutronclient2.2.4
Implements Blueprint: remove-use-of-quantum Change-Id: Idebe92d56d277435ffd23f292984f9b8b8fdb2df
Diffstat (limited to 'neutronclient')
-rw-r--r--neutronclient/__init__.py17
-rw-r--r--neutronclient/client.py250
-rw-r--r--neutronclient/common/__init__.py24
-rw-r--r--neutronclient/common/clientmanager.py87
-rw-r--r--neutronclient/common/command.py41
-rw-r--r--neutronclient/common/constants.py43
-rw-r--r--neutronclient/common/exceptions.py169
-rw-r--r--neutronclient/common/serializer.py410
-rw-r--r--neutronclient/common/utils.py200
-rw-r--r--neutronclient/neutron/__init__.py14
-rw-r--r--neutronclient/neutron/client.py64
-rw-r--r--neutronclient/neutron/v2_0/__init__.py588
-rw-r--r--neutronclient/neutron/v2_0/agent.py65
-rw-r--r--neutronclient/neutron/v2_0/agentscheduler.py234
-rw-r--r--neutronclient/neutron/v2_0/extension.py44
-rw-r--r--neutronclient/neutron/v2_0/floatingip.py151
-rw-r--r--neutronclient/neutron/v2_0/lb/__init__.py16
-rw-r--r--neutronclient/neutron/v2_0/lb/healthmonitor.py174
-rw-r--r--neutronclient/neutron/v2_0/lb/member.py99
-rw-r--r--neutronclient/neutron/v2_0/lb/pool.py124
-rw-r--r--neutronclient/neutron/v2_0/lb/vip.py115
-rw-r--r--neutronclient/neutron/v2_0/network.py152
-rw-r--r--neutronclient/neutron/v2_0/nvp_qos_queue.py89
-rw-r--r--neutronclient/neutron/v2_0/nvpnetworkgateway.py159
-rw-r--r--neutronclient/neutron/v2_0/port.py184
-rw-r--r--neutronclient/neutron/v2_0/quota.py232
-rw-r--r--neutronclient/neutron/v2_0/router.py230
-rw-r--r--neutronclient/neutron/v2_0/securitygroup.py259
-rw-r--r--neutronclient/neutron/v2_0/subnet.py168
-rw-r--r--neutronclient/openstack/__init__.py0
-rw-r--r--neutronclient/openstack/common/__init__.py0
-rw-r--r--neutronclient/openstack/common/exception.py142
-rw-r--r--neutronclient/openstack/common/gettextutils.py33
-rw-r--r--neutronclient/openstack/common/jsonutils.py148
-rw-r--r--neutronclient/openstack/common/strutils.py133
-rw-r--r--neutronclient/openstack/common/timeutils.py164
-rw-r--r--neutronclient/shell.py575
-rw-r--r--neutronclient/tests/unit/test_utils.py45
-rw-r--r--neutronclient/v2_0/__init__.py14
-rw-r--r--neutronclient/v2_0/client.py880
-rw-r--r--neutronclient/version.py22
41 files changed, 6558 insertions, 0 deletions
diff --git a/neutronclient/__init__.py b/neutronclient/__init__.py
new file mode 100644
index 0000000..034d66e
--- /dev/null
+++ b/neutronclient/__init__.py
@@ -0,0 +1,17 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Citrix 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.
+# @author: Tyler Smith, Cisco Systems
diff --git a/neutronclient/client.py b/neutronclient/client.py
new file mode 100644
index 0000000..9dc80a3
--- /dev/null
+++ b/neutronclient/client.py
@@ -0,0 +1,250 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+try:
+ import json
+except ImportError:
+ import simplejson as json
+import logging
+import os
+import urlparse
+# Python 2.5 compat fix
+if not hasattr(urlparse, 'parse_qsl'):
+ import cgi
+ urlparse.parse_qsl = cgi.parse_qsl
+
+import httplib2
+
+from neutronclient.common import exceptions
+from neutronclient.common import utils
+
+_logger = logging.getLogger(__name__)
+
+
+if os.environ.get('NEUTRONCLIENT_DEBUG'):
+ ch = logging.StreamHandler()
+ _logger.setLevel(logging.DEBUG)
+ _logger.addHandler(ch)
+
+
+class ServiceCatalog(object):
+ """Helper methods for dealing with a Keystone Service Catalog."""
+
+ def __init__(self, resource_dict):
+ self.catalog = resource_dict
+
+ def get_token(self):
+ """Fetch token details fron service catalog."""
+ token = {'id': self.catalog['access']['token']['id'],
+ 'expires': self.catalog['access']['token']['expires'], }
+ try:
+ token['user_id'] = self.catalog['access']['user']['id']
+ token['tenant_id'] = (
+ self.catalog['access']['token']['tenant']['id'])
+ except Exception:
+ # just leave the tenant and user out if it doesn't exist
+ pass
+ return token
+
+ def url_for(self, attr=None, filter_value=None,
+ service_type='network', endpoint_type='publicURL'):
+ """Fetch the URL from the Neutron service for
+ a particular endpoint type. If none given, return
+ publicURL.
+ """
+
+ catalog = self.catalog['access'].get('serviceCatalog', [])
+ matching_endpoints = []
+ for service in catalog:
+ if service['type'] != service_type:
+ continue
+
+ endpoints = service['endpoints']
+ for endpoint in endpoints:
+ if not filter_value or endpoint.get(attr) == filter_value:
+ matching_endpoints.append(endpoint)
+
+ if not matching_endpoints:
+ raise exceptions.EndpointNotFound()
+ elif len(matching_endpoints) > 1:
+ raise exceptions.AmbiguousEndpoints(message=matching_endpoints)
+ else:
+ if endpoint_type not in matching_endpoints[0]:
+ raise exceptions.EndpointTypeNotFound(message=endpoint_type)
+
+ return matching_endpoints[0][endpoint_type]
+
+
+class HTTPClient(httplib2.Http):
+ """Handles the REST calls and responses, include authn."""
+
+ USER_AGENT = 'python-neutronclient'
+
+ def __init__(self, username=None, tenant_name=None,
+ password=None, auth_url=None,
+ token=None, region_name=None, timeout=None,
+ endpoint_url=None, insecure=False,
+ endpoint_type='publicURL',
+ auth_strategy='keystone', **kwargs):
+ super(HTTPClient, self).__init__(timeout=timeout)
+ self.username = username
+ self.tenant_name = tenant_name
+ self.password = password
+ self.auth_url = auth_url.rstrip('/') if auth_url else None
+ self.endpoint_type = endpoint_type
+ self.region_name = region_name
+ self.auth_token = token
+ self.content_type = 'application/json'
+ self.endpoint_url = endpoint_url
+ self.auth_strategy = auth_strategy
+ # httplib2 overrides
+ self.force_exception_to_status_code = True
+ self.disable_ssl_certificate_validation = insecure
+
+ def _cs_request(self, *args, **kwargs):
+ kargs = {}
+ kargs.setdefault('headers', kwargs.get('headers', {}))
+ kargs['headers']['User-Agent'] = self.USER_AGENT
+
+ if 'content_type' in kwargs:
+ kargs['headers']['Content-Type'] = kwargs['content_type']
+ kargs['headers']['Accept'] = kwargs['content_type']
+ else:
+ kargs['headers']['Content-Type'] = self.content_type
+ kargs['headers']['Accept'] = self.content_type
+
+ if 'body' in kwargs:
+ kargs['body'] = kwargs['body']
+ args = utils.safe_encode_list(args)
+ kargs = utils.safe_encode_dict(kargs)
+ utils.http_log_req(_logger, args, kargs)
+ resp, body = self.request(*args, **kargs)
+ utils.http_log_resp(_logger, resp, body)
+ status_code = self.get_status_code(resp)
+ if status_code == 401:
+ raise exceptions.Unauthorized(message=body)
+ elif status_code == 403:
+ raise exceptions.Forbidden(message=body)
+ return resp, body
+
+ def authenticate_and_fetch_endpoint_url(self):
+ if not self.auth_token:
+ self.authenticate()
+ elif not self.endpoint_url:
+ self.endpoint_url = self._get_endpoint_url()
+
+ def do_request(self, url, method, **kwargs):
+ self.authenticate_and_fetch_endpoint_url()
+ # Perform the request once. If we get a 401 back then it
+ # might be because the auth token expired, so try to
+ # re-authenticate and try again. If it still fails, bail.
+ try:
+ kwargs.setdefault('headers', {})
+ kwargs['headers']['X-Auth-Token'] = self.auth_token
+ resp, body = self._cs_request(self.endpoint_url + url, method,
+ **kwargs)
+ return resp, body
+ except exceptions.Unauthorized:
+ self.authenticate()
+ kwargs.setdefault('headers', {})
+ kwargs['headers']['X-Auth-Token'] = self.auth_token
+ resp, body = self._cs_request(
+ self.endpoint_url + url, method, **kwargs)
+ return resp, body
+
+ def _extract_service_catalog(self, body):
+ """Set the client's service catalog from the response data."""
+ self.service_catalog = ServiceCatalog(body)
+ try:
+ sc = self.service_catalog.get_token()
+ self.auth_token = sc['id']
+ self.auth_tenant_id = sc.get('tenant_id')
+ self.auth_user_id = sc.get('user_id')
+ except KeyError:
+ raise exceptions.Unauthorized()
+ self.endpoint_url = self.service_catalog.url_for(
+ attr='region', filter_value=self.region_name,
+ endpoint_type=self.endpoint_type)
+
+ def authenticate(self):
+ if self.auth_strategy != 'keystone':
+ raise exceptions.Unauthorized(message='unknown auth strategy')
+ body = {'auth': {'passwordCredentials':
+ {'username': self.username,
+ 'password': self.password, },
+ 'tenantName': self.tenant_name, }, }
+
+ token_url = self.auth_url + "/tokens"
+
+ # Make sure we follow redirects when trying to reach Keystone
+ tmp_follow_all_redirects = self.follow_all_redirects
+ self.follow_all_redirects = True
+ try:
+ resp, body = self._cs_request(token_url, "POST",
+ body=json.dumps(body),
+ content_type="application/json")
+ finally:
+ self.follow_all_redirects = tmp_follow_all_redirects
+ status_code = self.get_status_code(resp)
+ if status_code != 200:
+ raise exceptions.Unauthorized(message=body)
+ if body:
+ try:
+ body = json.loads(body)
+ except ValueError:
+ pass
+ else:
+ body = None
+ self._extract_service_catalog(body)
+
+ def _get_endpoint_url(self):
+ url = self.auth_url + '/tokens/%s/endpoints' % self.auth_token
+ try:
+ resp, body = self._cs_request(url, "GET")
+ except exceptions.Unauthorized:
+ # rollback to authenticate() to handle case when neutron client
+ # is initialized just before the token is expired
+ self.authenticate()
+ return self.endpoint_url
+
+ body = json.loads(body)
+ for endpoint in body.get('endpoints', []):
+ if (endpoint['type'] == 'network' and
+ endpoint.get('region') == self.region_name):
+ if self.endpoint_type not in endpoint:
+ raise exceptions.EndpointTypeNotFound(
+ message=self.endpoint_type)
+ return endpoint[self.endpoint_type]
+
+ raise exceptions.EndpointNotFound()
+
+ def get_auth_info(self):
+ return {'auth_token': self.auth_token,
+ 'auth_tenant_id': self.auth_tenant_id,
+ 'auth_user_id': self.auth_user_id,
+ 'endpoint_url': self.endpoint_url}
+
+ def get_status_code(self, response):
+ """Returns the integer status code from the response.
+
+ Either a Webob.Response (used in testing) or httplib.Response
+ is returned.
+ """
+ if hasattr(response, 'status_int'):
+ return response.status_int
+ else:
+ return response.status
diff --git a/neutronclient/common/__init__.py b/neutronclient/common/__init__.py
new file mode 100644
index 0000000..47ca708
--- /dev/null
+++ b/neutronclient/common/__init__.py
@@ -0,0 +1,24 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright 2011 Nicira Networks, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+# @author: Somik Behera, Nicira Networks, Inc.
+
+import gettext
+
+t = gettext.translation('neutronclient', fallback=True)
+
+
+def _(msg):
+ return t.ugettext(msg)
diff --git a/neutronclient/common/clientmanager.py b/neutronclient/common/clientmanager.py
new file mode 100644
index 0000000..8e0614d
--- /dev/null
+++ b/neutronclient/common/clientmanager.py
@@ -0,0 +1,87 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+"""Manage access to the clients, including authenticating when needed.
+"""
+
+import logging
+
+from neutronclient import client
+from neutronclient.neutron import client as neutron_client
+
+
+LOG = logging.getLogger(__name__)
+
+
+class ClientCache(object):
+ """Descriptor class for caching created client handles.
+ """
+
+ def __init__(self, factory):
+ self.factory = factory
+ self._handle = None
+
+ def __get__(self, instance, owner):
+ # Tell the ClientManager to login to keystone
+ if self._handle is None:
+ self._handle = self.factory(instance)
+ return self._handle
+
+
+class ClientManager(object):
+ """Manages access to API clients, including authentication.
+ """
+ neutron = ClientCache(neutron_client.make_client)
+
+ def __init__(self, token=None, url=None,
+ auth_url=None,
+ endpoint_type=None,
+ tenant_name=None, tenant_id=None,
+ username=None, password=None,
+ region_name=None,
+ api_version=None,
+ auth_strategy=None,
+ insecure=False
+ ):
+ self._token = token
+ self._url = url
+ self._auth_url = auth_url
+ self._endpoint_type = endpoint_type
+ self._tenant_name = tenant_name
+ self._tenant_id = tenant_id
+ self._username = username
+ self._password = password
+ self._region_name = region_name
+ self._api_version = api_version
+ self._service_catalog = None
+ self._auth_strategy = auth_strategy
+ self._insecure = insecure
+ return
+
+ def initialize(self):
+ if not self._url:
+ httpclient = client.HTTPClient(username=self._username,
+ tenant_name=self._tenant_name,
+ password=self._password,
+ region_name=self._region_name,
+ auth_url=self._auth_url,
+ endpoint_type=self._endpoint_type,
+ insecure=self._insecure)
+ httpclient.authenticate()
+ # Populate other password flow attributes
+ self._token = httpclient.auth_token
+ self._url = httpclient.endpoint_url
diff --git a/neutronclient/common/command.py b/neutronclient/common/command.py
new file mode 100644
index 0000000..7191436
--- /dev/null
+++ b/neutronclient/common/command.py
@@ -0,0 +1,41 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+"""
+OpenStack base command
+"""
+
+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/common/constants.py b/neutronclient/common/constants.py
new file mode 100644
index 0000000..a8e8276
--- /dev/null
+++ b/neutronclient/common/constants.py
@@ -0,0 +1,43 @@
+# Copyright (c) 2012 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+EXT_NS = '_extension_ns'
+XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0'
+XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance"
+XSI_ATTR = "xsi:nil"
+XSI_NIL_ATTR = "xmlns:xsi"
+TYPE_XMLNS = "xmlns:quantum"
+TYPE_ATTR = "quantum:type"
+VIRTUAL_ROOT_KEY = "_v_root"
+ATOM_NAMESPACE = "http://www.w3.org/2005/Atom"
+ATOM_XMLNS = "xmlns:atom"
+ATOM_LINK_NOTATION = "{%s}link" % ATOM_NAMESPACE
+
+TYPE_BOOL = "bool"
+TYPE_INT = "int"
+TYPE_LONG = "long"
+TYPE_FLOAT = "float"
+TYPE_LIST = "list"
+TYPE_DICT = "dict"
+
+PLURALS = {'networks': 'network',
+ 'ports': 'port',
+ 'subnets': 'subnet',
+ 'dns_nameservers': 'dns_nameserver',
+ 'host_routes': 'host_route',
+ 'allocation_pools': 'allocation_pool',
+ 'fixed_ips': 'fixed_ip',
+ 'extensions': 'extension'}
diff --git a/neutronclient/common/exceptions.py b/neutronclient/common/exceptions.py
new file mode 100644
index 0000000..e02be30
--- /dev/null
+++ b/neutronclient/common/exceptions.py
@@ -0,0 +1,169 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Nicira Networks, Inc
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from neutronclient.common import _
+
+"""
+Neutron base exception handling.
+"""
+
+
+class NeutronException(Exception):
+ """Base Neutron Exception
+
+ Taken from nova.exception.NovaException
+ To correctly use this class, inherit from it and define
+ a 'message' property. That message will get printf'd
+ with the keyword arguments provided to the constructor.
+
+ """
+ message = _("An unknown exception occurred.")
+
+ def __init__(self, **kwargs):
+ try:
+ self._error_string = self.message % kwargs
+
+ except Exception:
+ # at least get the core message out if something happened
+ self._error_string = self.message
+
+ def __str__(self):
+ return self._error_string
+
+
+class NotFound(NeutronException):
+ pass
+
+
+class NeutronClientException(NeutronException):
+
+ def __init__(self, **kwargs):
+ message = kwargs.get('message')
+ self.status_code = kwargs.get('status_code', 0)
+ if message:
+ self.message = message
+ super(NeutronClientException, self).__init__(**kwargs)
+
+
+# NOTE: on the client side, we use different exception types in order
+# to allow client library users to handle server exceptions in try...except
+# blocks. The actual error message is the one generated on the server side
+class NetworkNotFoundClient(NeutronClientException):
+ pass
+
+
+class PortNotFoundClient(NeutronClientException):
+ pass
+
+
+class MalformedResponseBody(NeutronException):
+ message = _("Malformed response body: %(reason)s")
+
+
+class StateInvalidClient(NeutronClientException):
+ pass
+
+
+class NetworkInUseClient(NeutronClientException):
+ pass
+
+
+class PortInUseClient(NeutronClientException):
+ pass
+
+
+class AlreadyAttachedClient(NeutronClientException):
+ pass
+
+
+class Unauthorized(NeutronClientException):
+ message = _("Unauthorized: bad credentials.")
+
+
+class Forbidden(NeutronClientException):
+ message = _("Forbidden: your credentials don't give you access to this "
+ "resource.")
+
+
+class EndpointNotFound(NeutronClientException):
+ """Could not find Service or Region in Service Catalog."""
+ message = _("Could not find Service or Region in Service Catalog.")
+
+
+class EndpointTypeNotFound(NeutronClientException):
+ """Could not find endpoint type in Service Catalog."""
+
+ def __str__(self):
+ msg = "Could not find endpoint type %s in Service Catalog."
+ return msg % repr(self.message)
+
+
+class AmbiguousEndpoints(NeutronClientException):
+ """Found more than one matching endpoint in Service Catalog."""
+
+ def __str__(self):
+ return "AmbiguousEndpoints: %s" % repr(self.message)
+
+
+class NeutronCLIError(NeutronClientException):
+ """Exception raised when command line parsing fails."""
+ pass
+
+
+class RequestURITooLong(NeutronClientException):
+ """Raised when a request fails with HTTP error 414."""
+
+ def __init__(self, **kwargs):
+ self.excess = kwargs.get('excess', 0)
+ super(RequestURITooLong, self).__init__(**kwargs)
+
+
+class ConnectionFailed(NeutronClientException):
+ message = _("Connection to neutron failed: %(reason)s")
+
+
+class BadInputError(Exception):
+ """Error resulting from a client sending bad input to a server."""
+ pass
+
+
+class Error(Exception):
+ def __init__(self, message=None):
+ super(Error, self).__init__(message)
+
+
+class MalformedRequestBody(NeutronException):
+ message = _("Malformed request body: %(reason)s")
+
+
+class Invalid(Error):
+ pass
+
+
+class InvalidContentType(Invalid):
+ message = _("Invalid content type %(content_type)s.")
+
+
+class UnsupportedVersion(Exception):
+ """Indicates that the user is trying to use an unsupported
+ version of the API
+ """
+ pass
+
+
+class CommandError(Exception):
+ pass
diff --git a/neutronclient/common/serializer.py b/neutronclient/common/serializer.py
new file mode 100644
index 0000000..b8f1c19
--- /dev/null
+++ b/neutronclient/common/serializer.py
@@ -0,0 +1,410 @@
+# Copyright 2013 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+###
+### Codes from neutron wsgi
+###
+
+import logging
+
+from xml.etree import ElementTree as etree
+from xml.parsers import expat
+
+from neutronclient.common import constants
+from neutronclient.common import exceptions as exception
+from neutronclient.openstack.common.gettextutils import _
+from neutronclient.openstack.common import jsonutils
+
+LOG = logging.getLogger(__name__)
+
+
+class ActionDispatcher(object):
+ """Maps method name to local methods through action name."""
+
+ def dispatch(self, *args, **kwargs):
+ """Find and call local method."""
+ action = kwargs.pop('action', 'default')
+ action_method = getattr(self, str(action), self.default)
+ return action_method(*args, **kwargs)
+
+ def default(self, data):
+ raise NotImplementedError()
+
+
+class DictSerializer(ActionDispatcher):
+ """Default request body serialization."""
+
+ def serialize(self, data, action='default'):
+ return self.dispatch(data, action=action)
+
+ def default(self, data):
+ return ""
+
+
+class JSONDictSerializer(DictSerializer):
+ """Default JSON request body serialization."""
+
+ def default(self, data):
+ def sanitizer(obj):
+ return unicode(obj)
+ return jsonutils.dumps(data, default=sanitizer)
+
+
+class XMLDictSerializer(DictSerializer):
+
+ def __init__(self, metadata=None, xmlns=None):
+ """XMLDictSerializer constructor.
+
+ :param metadata: information needed to deserialize xml into
+ a dictionary.
+ :param xmlns: XML namespace to include with serialized xml
+ """
+ super(XMLDictSerializer, self).__init__()
+ self.metadata = metadata or {}
+ if not xmlns:
+ xmlns = self.metadata.get('xmlns')
+ if not xmlns:
+ xmlns = constants.XML_NS_V20
+ self.xmlns = xmlns
+
+ def default(self, data):
+ """Default serializer of XMLDictSerializer.
+
+ :param data: expect data to contain a single key as XML root, or
+ contain another '*_links' key as atom links. Other
+ case will use 'VIRTUAL_ROOT_KEY' as XML root.
+ """
+ try:
+ links = None
+ has_atom = False
+ if data is None:
+ root_key = constants.VIRTUAL_ROOT_KEY
+ root_value = None
+ else:
+ link_keys = [k for k in data.iterkeys() or []
+ if k.endswith('_links')]
+ if link_keys:
+ links = data.pop(link_keys[0], None)
+ has_atom = True
+ root_key = (len(data) == 1 and
+ data.keys()[0] or constants.VIRTUAL_ROOT_KEY)
+ root_value = data.get(root_key, data)
+ doc = etree.Element("_temp_root")
+ used_prefixes = []
+ self._to_xml_node(doc, self.metadata, root_key,
+ root_value, used_prefixes)
+ if links:
+ self._create_link_nodes(list(doc)[0], links)
+ return self.to_xml_string(list(doc)[0], used_prefixes, has_atom)
+ except AttributeError as e:
+ LOG.exception(str(e))
+ return ''
+
+ def __call__(self, data):
+ # Provides a migration path to a cleaner WSGI layer, this
+ # "default" stuff and extreme extensibility isn't being used
+ # like originally intended
+ return self.default(data)
+
+ def to_xml_string(self, node, used_prefixes, has_atom=False):
+ self._add_xmlns(node, used_prefixes, has_atom)
+ return etree.tostring(node, encoding='UTF-8')
+
+ #NOTE (ameade): the has_atom should be removed after all of the
+ # xml serializers and view builders have been updated to the current
+ # spec that required all responses include the xmlns:atom, the has_atom
+ # flag is to prevent current tests from breaking
+ def _add_xmlns(self, node, used_prefixes, has_atom=False):
+ node.set('xmlns', self.xmlns)
+ node.set(constants.TYPE_XMLNS, self.xmlns)
+ if has_atom:
+ node.set(constants.ATOM_XMLNS, constants.ATOM_NAMESPACE)
+ node.set(constants.XSI_NIL_ATTR, constants.XSI_NAMESPACE)
+ ext_ns = self.metadata.get(constants.EXT_NS, {})
+ for prefix in used_prefixes:
+ if prefix in ext_ns:
+ node.set('xmlns:' + prefix, ext_ns[prefix])
+
+ def _to_xml_node(self, parent, metadata, nodename, data, used_prefixes):
+ """Recursive method to convert data members to XML nodes."""
+ result = etree.SubElement(parent, nodename)
+ if ":" in nodename:
+ used_prefixes.append(nodename.split(":", 1)[0])
+ #TODO(bcwaldon): accomplish this without a type-check
+ if isinstance(data, list):
+ if not data:
+ result.set(
+ constants.TYPE_ATTR,
+ constants.TYPE_LIST)
+ return result
+ singular = metadata.get('plurals', {}).get(nodename, None)
+ if singular is None:
+ if nodename.endswith('s'):
+ singular = nodename[:-1]
+ else:
+ singular = 'item'
+ for item in data:
+ self._to_xml_node(result, metadata, singular, item,
+ used_prefixes)
+ #TODO(bcwaldon): accomplish this without a type-check
+ elif isinstance(data, dict):
+ if not data:
+ result.set(
+ constants.TYPE_ATTR,
+ constants.TYPE_DICT)
+ return result
+ attrs = metadata.get('attributes', {}).get(nodename, {})
+ for k, v in data.items():
+ if k in attrs:
+ result.set(k, str(v))
+ else:
+ self._to_xml_node(result, metadata, k, v,
+ used_prefixes)
+ elif data is None:
+ result.set(constants.XSI_ATTR, 'true')
+ else:
+ if isinstance(data, bool):
+ result.set(
+ constants.TYPE_ATTR,
+ constants.TYPE_BOOL)
+ elif isinstance(data, int):
+ result.set(
+ constants.TYPE_ATTR,
+ constants.TYPE_INT)
+ elif isinstance(data, long):
+ result.set(
+ constants.TYPE_ATTR,
+ constants.TYPE_LONG)
+ elif isinstance(data, float):
+ result.set(
+ constants.TYPE_ATTR,
+ constants.TYPE_FLOAT)
+ LOG.debug(_("Data %(data)s type is %(type)s"),
+ {'data': data,
+ 'type': type(data)})
+ if isinstance(data, str):
+ result.text = unicode(data, 'utf-8')
+ else:
+ result.text = unicode(data)
+ return result
+
+ def _create_link_nodes(self, xml_doc, links):
+ for link in links:
+ link_node = etree.SubElement(xml_doc, 'atom:link')
+ link_node.set('rel', link['rel'])
+ link_node.set('href', link['href'])
+
+
+class TextDeserializer(ActionDispatcher):
+ """Default request body deserialization."""
+
+ def deserialize(self, datastring, action='default'):
+ return self.dispatch(datastring, action=action)
+
+ def default(self, datastring):
+ return {}
+
+
+class JSONDeserializer(TextDeserializer):
+
+ def _from_json(self, datastring):
+ try:
+ return jsonutils.loads(datastring)
+ except ValueError:
+ msg = _("Cannot understand JSON")
+ raise exception.MalformedRequestBody(reason=msg)
+
+ def default(self, datastring):
+ return {'body': self._from_json(datastring)}
+
+
+class XMLDeserializer(TextDeserializer):
+
+ def __init__(self, metadata=None):
+ """XMLDeserializer constructor.
+
+ :param metadata: information needed to deserialize xml into
+ a dictionary.
+ """
+ super(XMLDeserializer, self).__init__()
+ self.metadata = metadata or {}
+ xmlns = self.metadata.get('xmlns')
+ if not xmlns:
+ xmlns = constants.XML_NS_V20
+ self.xmlns = xmlns
+
+ def _get_key(self, tag):
+ tags = tag.split("}", 1)
+ if len(tags) == 2:
+ ns = tags[0][1:]
+ bare_tag = tags[1]
+ ext_ns = self.metadata.get(constants.EXT_NS, {})
+ if ns == self.xmlns:
+ return bare_tag
+ for prefix, _ns in ext_ns.items():
+ if ns == _ns:
+ return prefix + ":" + bare_tag
+ else:
+ return tag
+
+ def _get_links(self, root_tag, node):
+ link_nodes = node.findall(constants.ATOM_LINK_NOTATION)
+ root_tag = self._get_key(node.tag)
+ link_key = "%s_links" % root_tag
+ link_list = []
+ for link in link_nodes:
+ link_list.append({'rel': link.get('rel'),
+ 'href': link.get('href')})
+ # Remove link node in order to avoid link node being
+ # processed as an item in _from_xml_node
+ node.remove(link)
+ return link_list and {link_key: link_list} or {}
+
+ def _from_xml(self, datastring):
+ if datastring is None:
+ return None
+ plurals = set(self.metadata.get('plurals', {}))
+ try:
+ node = etree.fromstring(datastring)
+ root_tag = self._get_key(node.tag)
+ links = self._get_links(root_tag, node)
+ result = self._from_xml_node(node, plurals)
+ # There is no case where root_tag = constants.VIRTUAL_ROOT_KEY
+ # and links is not None because of the way data are serialized
+ if root_tag == constants.VIRTUAL_ROOT_KEY:
+ return result
+ return dict({root_tag: result}, **links)
+ except Exception as e:
+ parseError = False
+ # Python2.7
+ if (hasattr(etree, 'ParseError') and
+ isinstance(e, getattr(etree, 'ParseError'))):
+ parseError = True
+ # Python2.6
+ elif isinstance(e, expat.ExpatError):
+ parseError = True
+ if parseError:
+ msg = _("Cannot understand XML")
+ raise exception.MalformedRequestBody(reason=msg)
+ else:
+ raise
+
+ def _from_xml_node(self, node, listnames):
+ """Convert a minidom node to a simple Python type.
+
+ :param listnames: list of XML node names whose subnodes should
+ be considered list items.
+
+ """
+ attrNil = node.get(str(etree.QName(constants.XSI_NAMESPACE, "nil")))
+ attrType = node.get(str(etree.QName(
+ self.metadata.get('xmlns'), "type")))
+ if (attrNil and attrNil.lower() == 'true'):
+ return None
+ elif not len(node) and not node.text:
+ if (attrType and attrType == constants.TYPE_DICT):
+ return {}
+ elif (attrType and attrType == constants.TYPE_LIST):
+ return []
+ else:
+ return ''
+ elif (len(node) == 0 and node.text):
+ converters = {constants.TYPE_BOOL:
+ lambda x: x.lower() == 'true',
+ constants.TYPE_INT:
+ lambda x: int(x),
+ constants.TYPE_LONG:
+ lambda x: long(x),
+ constants.TYPE_FLOAT:
+ lambda x: float(x)}
+ if attrType and attrType in converters:
+ return converters[attrType](node.text)
+ else:
+ return node.text
+ elif self._get_key(node.tag) in listnames:
+ return [self._from_xml_node(n, listnames) for n in node]
+ else:
+ result = dict()
+ for attr in node.keys():
+ if (attr == 'xmlns' or
+ attr.startswith('xmlns:') or
+ attr == constants.XSI_ATTR or
+ attr == constants.TYPE_ATTR):
+ continue
+ result[self._get_key(attr)] = node.get(attr)
+ children = list(node)
+ for child in children:
+ result[self._get_key(child.tag)] = self._from_xml_node(
+ child, listnames)
+ return result
+
+ def default(self, datastring):
+ return {'body': self._from_xml(datastring)}
+
+ def __call__(self, datastring):
+ # Adding a migration path to allow us to remove unncessary classes
+ return self.default(datastring)
+
+
+# NOTE(maru): this class is duplicated from neutron.wsgi
+class Serializer(object):
+ """Serializes and deserializes dictionaries to certain MIME types."""
+
+ def __init__(self, metadata=None, default_xmlns=None):
+ """Create a serializer based on the given WSGI environment.
+
+ 'metadata' is an optional dict mapping MIME types to information
+ needed to serialize a dictionary to that type.
+
+ """
+ self.metadata = metadata or {}
+ self.default_xmlns = default_xmlns
+
+ def _get_serialize_handler(self, content_type):
+ handlers = {
+ 'application/json': JSONDictSerializer(),
+ 'application/xml': XMLDictSerializer(self.metadata),
+ }
+
+ try:
+ return handlers[content_type]
+ except Exception:
+ raise exception.InvalidContentType(content_type=content_type)
+
+ def serialize(self, data, content_type):
+ """Serialize a dictionary into the specified content type."""
+ return self._get_serialize_handler(content_type).serialize(data)
+
+ def deserialize(self, datastring, content_type):
+ """Deserialize a string to a dictionary.
+
+ The string must be in the format of a supported MIME type.
+
+ """
+ return self.get_deserialize_handler(content_type).deserialize(
+ datastring)
+
+ def get_deserialize_handler(self, content_type):
+ handlers = {
+ 'application/json': JSONDeserializer(),
+ 'application/xml': XMLDeserializer(self.metadata),
+ }
+
+ try:
+ return handlers[content_type]
+ except Exception:
+ raise exception.InvalidContentType(content_type=content_type)
diff --git a/neutronclient/common/utils.py b/neutronclient/common/utils.py
new file mode 100644
index 0000000..ca2e6ec
--- /dev/null
+++ b/neutronclient/common/utils.py
@@ -0,0 +1,200 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011, Nicira Networks, Inc.
+#
+# 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.
+#
+# Borrowed from nova code base, more utilities will be added/borrowed as and
+# when needed.
+# @author: Somik Behera, Nicira Networks, Inc.
+
+"""Utilities and helper functions."""
+
+import datetime
+import json
+import logging
+import os
+import sys
+
+from neutronclient.common import exceptions
+from neutronclient.openstack.common import strutils
+
+
+def env(*vars, **kwargs):
+ """Returns the first environment variable set.
+
+ if none are non-empty, defaults to '' or keyword arg default.
+ """
+ for v in vars:
+ value = os.environ.get(v)
+ if value:
+ return value
+ return kwargs.get('default', '')
+
+
+def to_primitive(value):
+ if isinstance(value, list) or isinstance(value, tuple):
+ o = []
+ for v in value:
+ o.append(to_primitive(v))
+ return o
+ elif isinstance(value, dict):
+ o = {}
+ for k, v in value.iteritems():
+ o[k] = to_primitive(v)
+ return o
+ elif isinstance(value, datetime.datetime):
+ return str(value)
+ elif hasattr(value, 'iteritems'):
+ return to_primitive(dict(value.iteritems()))
+ elif hasattr(value, '__iter__'):
+ return to_primitive(list(value))
+ else:
+ return value
+
+
+def dumps(value, indent=None):
+ try:
+ return json.dumps(value, indent=indent)
+ except TypeError:
+ pass
+ return json.dumps(to_primitive(value))
+
+
+def loads(s):
+ return json.loads(s)
+
+
+def import_class(import_str):
+ """Returns a class from a string including module and class.
+
+ :param import_str: a string representation of the class name
+ :rtype: the requested class
+ """
+ mod_str, _sep, class_str = import_str.rpartition('.')
+ __import__(mod_str)
+ return getattr(sys.modules[mod_str], class_str)
+
+
+def get_client_class(api_name, version, version_map):
+ """Returns the client class for the requested API version
+
+ :param api_name: the name of the API, e.g. 'compute', 'image', etc
+ :param version: the requested API version
+ :param version_map: a dict of client classes keyed by version
+ :rtype: a client class for the requested API version
+ """
+ try:
+ client_path = version_map[str(version)]
+ except (KeyError, ValueError):
+ msg = "Invalid %s client version '%s'. must be one of: %s" % (
+ (api_name, version, ', '.join(version_map.keys())))
+ raise exceptions.UnsupportedVersion(msg)
+
+ return import_class(client_path)
+
+
+def get_item_properties(item, fields, mixed_case_fields=[], formatters={}):
+ """Return a tuple containing the item properties.
+
+ :param item: a single item resource (e.g. Server, Tenant, etc)
+ :param fields: tuple of strings with the desired field names
+ :param mixed_case_fields: tuple of field names to preserve case
+ :param formatters: dictionary mapping field names to callables
+ to format the values
+ """
+ row = []
+
+ for field in fields:
+ if field in formatters:
+ row.append(formatters[field](item))
+ else:
+ if field in mixed_case_fields:
+ field_name = field.replace(' ', '_')
+ else:
+ field_name = field.lower().replace(' ', '_')
+ if not hasattr(item, field_name) and isinstance(item, dict):
+ data = item[field_name]
+ else:
+ data = getattr(item, field_name, '')
+ if data is None:
+ data = ''
+ row.append(data)
+ return tuple(row)
+
+
+def str2bool(strbool):
+ if strbool is None:
+ return None
+ else:
+ return strbool.lower() == 'true'
+
+
+def str2dict(strdict):
+ '''Convert key1=value1,key2=value2,... string into dictionary.
+
+ :param strdict: key1=value1,key2=value2
+ '''
+ _info = {}
+ for kv_str in strdict.split(","):
+ k, v = kv_str.split("=", 1)
+ _info.update({k: v})
+ return _info
+
+
+def http_log_req(_logger, args, kwargs):
+ if not _logger.isEnabledFor(logging.DEBUG):
+ return
+
+ string_parts = ['curl -i']
+ for element in args:
+ if element in ('GET', 'POST', 'DELETE', 'PUT'):
+ string_parts.append(' -X %s' % element)
+ else:
+ string_parts.append(' %s' % element)
+
+ for element in kwargs['headers']:
+ header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
+ string_parts.append(header)
+
+ if 'body' in kwargs and kwargs['body']:
+ string_parts.append(" -d '%s'" % (kwargs['body']))
+ string_parts = safe_encode_list(string_parts)
+ _logger.debug("\nREQ: %s\n" % "".join(string_parts))
+
+
+def http_log_resp(_logger, resp, body):
+ if not _logger.isEnabledFor(logging.DEBUG):
+ return
+ _logger.debug("RESP:%s %s\n", resp, body)
+
+
+def _safe_encode_without_obj(data):
+ if isinstance(data, basestring):
+ return strutils.safe_encode(data)
+ return data
+
+
+def safe_encode_list(data):
+ return map(_safe_encode_without_obj, data)
+
+
+def safe_encode_dict(data):
+ def _encode_item((k, v)):
+ if isinstance(v, list):
+ return (k, safe_encode_list(v))
+ elif isinstance(v, dict):
+ return (k, safe_encode_dict(v))
+ return (k, _safe_encode_without_obj(v))
+
+ return dict(map(_encode_item, data.items()))
diff --git a/neutronclient/neutron/__init__.py b/neutronclient/neutron/__init__.py
new file mode 100644
index 0000000..63c3905
--- /dev/null
+++ b/neutronclient/neutron/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
diff --git a/neutronclient/neutron/client.py b/neutronclient/neutron/client.py
new file mode 100644
index 0000000..daf0481
--- /dev/null
+++ b/neutronclient/neutron/client.py
@@ -0,0 +1,64 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+from neutronclient.common import exceptions
+from neutronclient.common import utils
+
+
+API_NAME = 'network'
+API_VERSIONS = {
+ '2.0': 'neutronclient.v2_0.client.Client',
+}
+
+
+def make_client(instance):
+ """Returns an neutron client.
+ """
+ neutron_client = utils.get_client_class(
+ API_NAME,
+ instance._api_version[API_NAME],
+ API_VERSIONS,
+ )
+ instance.initialize()
+ url = instance._url
+ url = url.rstrip("/")
+ if '2.0' == instance._api_version[API_NAME]:
+ client = neutron_client(username=instance._username,
+ tenant_name=instance._tenant_name,
+ password=instance._password,
+ region_name=instance._region_name,
+ auth_url=instance._auth_url,
+ endpoint_url=url,
+ token=instance._token,
+ auth_strategy=instance._auth_strategy,
+ insecure=instance._insecure)
+ return client
+ else:
+ raise exceptions.UnsupportedVersion("API version %s is not supported" %
+ instance._api_version[API_NAME])
+
+
+def Client(api_version, *args, **kwargs):
+ """Return an neutron client.
+ @param api_version: only 2.0 is supported now
+ """
+ neutron_client = utils.get_client_class(
+ API_NAME,
+ api_version,
+ API_VERSIONS,
+ )
+ return neutron_client(*args, **kwargs)
diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py
new file mode 100644
index 0000000..9cb8cd1
--- /dev/null
+++ b/neutronclient/neutron/v2_0/__init__.py
@@ -0,0 +1,588 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import argparse
+import logging
+import re
+
+from cliff.formatters import table
+from cliff import lister
+from cliff import show
+
+from neutronclient.common import command
+from neutronclient.common import exceptions
+from neutronclient.common import utils
+from neutronclient.openstack.common.gettextutils import _
+
+HEX_ELEM = '[0-9A-Fa-f]'
+UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}',
+ HEX_ELEM + '{4}', HEX_ELEM + '{4}',
+ HEX_ELEM + '{12}'])
+
+
+def find_resourceid_by_name_or_id(client, resource, name_or_id):
+ obj_lister = getattr(client, "list_%ss" % resource)
+ # perform search by id only if we are passing a valid UUID
+ match = re.match(UUID_PATTERN, name_or_id)
+ collection = resource + "s"
+ if match:
+ data = obj_lister(id=name_or_id, fields='id')
+ if data and data[collection]:
+ return data[collection][0]['id']
+ return _find_resourceid_by_name(client, resource, name_or_id)
+
+
+def _find_resourceid_by_name(client, resource, name):
+ obj_lister = getattr(client, "list_%ss" % resource)
+ data = obj_lister(name=name, fields='id')
+ collection = resource + "s"
+ info = data[collection]
+ if len(info) > 1:
+ msg = (_("Multiple %(resource)s matches found for name '%(name)s',"
+ " use an ID to be more specific.") %
+ {'resource': resource, 'name': name})
+ raise exceptions.NeutronClientException(
+ message=msg)
+ elif len(info) == 0:
+ 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)
+ else:
+ return info[0]['id']
+
+
+def add_show_list_common_argument(parser):
+ parser.add_argument(
+ '-D', '--show-details',
+ help='show detailed info',
+ action='store_true',
+ default=False, )
+ parser.add_argument(
+ '--show_details',
+ action='store_true',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--fields',
+ help=argparse.SUPPRESS,
+ action='append',
+ default=[])
+ parser.add_argument(
+ '-F', '--field',
+ dest='fields', metavar='FIELD',
+ help='specify the field(s) to be returned by server,'
+ ' can be repeated',
+ action='append',
+ default=[])
+
+
+def add_pagination_argument(parser):
+ parser.add_argument(
+ '-P', '--page-size',
+ dest='page_size', metavar='SIZE', type=int,
+ help=("specify retrieve unit of each request, then split one request "
+ "to several requests"),
+ default=None)
+
+
+def add_sorting_argument(parser):
+ parser.add_argument(
+ '--sort-key',
+ dest='sort_key', metavar='FIELD',
+ action='append',
+ help=("sort list by specified fields (This option can be repeated), "
+ "The number of sort_dir and sort_key should match each other, "
+ "more sort_dir specified will be omitted, less will be filled "
+ "with asc as default direction "),
+ default=[])
+ parser.add_argument(
+ '--sort-dir',
+ dest='sort_dir', metavar='{asc,desc}',
+ help=("sort list in specified directions "
+ "(This option can be repeated)"),
+ action='append',
+ default=[],
+ choices=['asc', 'desc'])
+
+
+def is_number(s):
+ try:
+ float(s) # for int, long and float
+ except ValueError:
+ try:
+ complex(s) # for complex
+ except ValueError:
+ return False
+
+ return True
+
+
+def parse_args_to_dict(values_specs):
+ '''It is used to analyze the extra command options to command.
+
+ Besides known options and arguments, our commands also support user to
+ put more options to the end of command line. For example,
+ list_nets -- --tag x y --key1 value1, where '-- --tag x y --key1 value1'
+ is extra options to our list_nets. This feature can support V2.0 API's
+ fields selection and filters. For example, to list networks which has name
+ 'test4', we can have list_nets -- --name=test4.
+
+ value spec is: --key type=int|bool|... value. Type is one of Python
+ built-in types. By default, type is string. The key without value is
+ a bool option. Key with two values will be a list option.
+
+ '''
+ # -- is a pseudo argument
+ values_specs_copy = values_specs[:]
+ if values_specs_copy and values_specs_copy[0] == '--':
+ del values_specs_copy[0]
+ _options = {}
+ current_arg = None
+ _values_specs = []
+ _value_number = 0
+ _list_flag = False
+ current_item = None
+ for _item in values_specs_copy:
+ if _item.startswith('--'):
+ if current_arg is not None:
+ if _value_number > 1 or _list_flag:
+ current_arg.update({'nargs': '+'})
+ elif _value_number == 0:
+ current_arg.update({'action': 'store_true'})
+ _temp = _item
+ if "=" in _item:
+ _item = _item.split('=')[0]
+ if _item in _options:
+ raise exceptions.CommandError(
+ "duplicated options %s" % ' '.join(values_specs))
+ else:
+ _options.update({_item: {}})
+ current_arg = _options[_item]
+ _item = _temp
+ elif _item.startswith('type='):
+ if current_arg is None:
+ raise exceptions.CommandError(
+ "invalid values_specs %s" % ' '.join(values_specs))
+ if 'type' not in current_arg:
+ _type_str = _item.split('=', 2)[1]
+ current_arg.update({'type': eval(_type_str)})
+ if _type_str == 'bool':
+ current_arg.update({'type': utils.str2bool})
+ elif _type_str == 'dict':
+ current_arg.update({'type': utils.str2dict})
+ continue
+ elif _item == 'list=true':
+ _list_flag = True
+ continue
+ if not _item.startswith('--'):
+ if (not current_item or '=' in current_item or
+ _item.startswith('-') and not is_number(_item)):
+ raise exceptions.CommandError(
+ "Invalid values_specs %s" % ' '.join(values_specs))
+ _value_number += 1
+ elif _item.startswith('--'):
+ current_item = _item
+ if '=' in current_item:
+ _value_number = 1
+ else:
+ _value_number = 0
+ _list_flag = False
+ _values_specs.append(_item)
+ if current_arg is not None:
+ if _value_number > 1 or _list_flag:
+ current_arg.update({'nargs': '+'})
+ elif _value_number == 0:
+ current_arg.update({'action': 'store_true'})
+ _args = None
+ if _values_specs:
+ _parser = argparse.ArgumentParser(add_help=False)
+ for opt, optspec in _options.iteritems():
+ _parser.add_argument(opt, **optspec)
+ _args = _parser.parse_args(_values_specs)
+ result_dict = {}
+ if _args:
+ for opt in _options.iterkeys():
+ _opt = opt.split('--', 2)[1]
+ _opt = _opt.replace('-', '_')
+ _value = getattr(_args, _opt)
+ if _value is not None:
+ result_dict.update({_opt: _value})
+ return result_dict
+
+
+def _merge_args(qCmd, parsed_args, _extra_values, value_specs):
+ """Merge arguments from _extra_values into parsed_args.
+
+ If an argument value are provided in both and it is a list,
+ the values in _extra_values will be merged into parsed_args.
+
+ @param parsed_args: the parsed args from known options
+ @param _extra_values: the other parsed arguments in unknown parts
+ @param values_specs: the unparsed unknown parts
+ """
+ temp_values = _extra_values.copy()
+ for key, value in temp_values.iteritems():
+ if hasattr(parsed_args, key):
+ arg_value = getattr(parsed_args, key)
+ if arg_value is not None and value is not None:
+ if isinstance(arg_value, list):
+ if value and isinstance(value, list):
+ if type(arg_value[0]) == type(value[0]):
+ arg_value.extend(value)
+ _extra_values.pop(key)
+
+
+def update_dict(obj, dict, attributes):
+ for attribute in attributes:
+ if hasattr(obj, attribute) and getattr(obj, attribute):
+ 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')
+
+
+class NeutronCommand(command.OpenStackCommand):
+ api = 'network'
+ log = logging.getLogger(__name__ + '.NeutronCommand')
+ values_specs = []
+ json_indent = None
+
+ def __init__(self, app, app_args):
+ super(NeutronCommand, self).__init__(app, app_args)
+ if hasattr(self, 'formatters'):
+ self.formatters['table'] = TableFormater()
+
+ def get_client(self):
+ return self.app.client_manager.neutron
+
+ def get_parser(self, prog_name):
+ parser = super(NeutronCommand, self).get_parser(prog_name)
+ parser.add_argument(
+ '--request-format',
+ help=_('the xml or json request format'),
+ default='json',
+ choices=['json', 'xml', ], )
+ parser.add_argument(
+ '--request_format',
+ choices=['json', 'xml', ],
+ help=argparse.SUPPRESS)
+
+ return parser
+
+ def format_output_data(self, data):
+ # Modify data to make it more readable
+ if self.resource in data:
+ for k, v in data[self.resource].iteritems():
+ if isinstance(v, list):
+ value = '\n'.join(utils.dumps(
+ i, indent=self.json_indent) if isinstance(i, dict)
+ else str(i) for i in v)
+ data[self.resource][k] = value
+ elif isinstance(v, dict):
+ value = utils.dumps(v, indent=self.json_indent)
+ data[self.resource][k] = value
+ elif v is None:
+ data[self.resource][k] = ''
+
+ def add_known_arguments(self, parser):
+ pass
+
+ def args2body(self, parsed_args):
+ return {}
+
+
+class CreateCommand(NeutronCommand, show.ShowOne):
+ """Create a resource for a given tenant
+
+ """
+
+ api = 'network'
+ resource = None
+ log = None
+
+ def get_parser(self, prog_name):
+ parser = super(CreateCommand, self).get_parser(prog_name)
+ parser.add_argument(
+ '--tenant-id', metavar='TENANT_ID',
+ help=_('the owner tenant ID'), )
+ parser.add_argument(
+ '--tenant_id',
+ help=argparse.SUPPRESS)
+ self.add_known_arguments(parser)
+ return parser
+
+ def get_data(self, parsed_args):
+ self.log.debug('get_data(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ _extra_values = parse_args_to_dict(self.values_specs)
+ _merge_args(self, parsed_args, _extra_values,
+ self.values_specs)
+ body = self.args2body(parsed_args)
+ body[self.resource].update(_extra_values)
+ obj_creator = getattr(neutron_client,
+ "create_%s" % self.resource)
+ data = obj_creator(body)
+ self.format_output_data(data)
+ # {u'network': {u'id': u'e9424a76-6db4-4c93-97b6-ec311cd51f19'}}
+ info = self.resource in data and data[self.resource] or None
+ if info:
+ print >>self.app.stdout, _('Created a new %s:') % self.resource
+ else:
+ info = {'': ''}
+ return zip(*sorted(info.iteritems()))
+
+
+class UpdateCommand(NeutronCommand):
+ """Update resource's information
+ """
+
+ api = 'network'
+ resource = None
+ log = None
+
+ def get_parser(self, prog_name):
+ parser = super(UpdateCommand, self).get_parser(prog_name)
+ parser.add_argument(
+ 'id', metavar=self.resource.upper(),
+ help='ID or name of %s to update' % self.resource)
+ self.add_known_arguments(parser)
+ return parser
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ _extra_values = parse_args_to_dict(self.values_specs)
+ _merge_args(self, parsed_args, _extra_values,
+ self.values_specs)
+ body = self.args2body(parsed_args)
+ if self.resource in body:
+ body[self.resource].update(_extra_values)
+ else:
+ body[self.resource] = _extra_values
+ if not body[self.resource]:
+ raise exceptions.CommandError(
+ "Must specify new values to update %s" % self.resource)
+ _id = find_resourceid_by_name_or_id(neutron_client,
+ self.resource,
+ parsed_args.id)
+ obj_updator = getattr(neutron_client,
+ "update_%s" % self.resource)
+ obj_updator(_id, body)
+ print >>self.app.stdout, (
+ _('Updated %(resource)s: %(id)s') %
+ {'id': parsed_args.id, 'resource': self.resource})
+ return
+
+
+class DeleteCommand(NeutronCommand):
+ """Delete a given resource
+
+ """
+
+ api = 'network'
+ resource = None
+ log = None
+ allow_names = True
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteCommand, self).get_parser(prog_name)
+ if self.allow_names:
+ help_str = 'ID or name of %s to delete'
+ else:
+ help_str = 'ID of %s to delete'
+ parser.add_argument(
+ 'id', metavar=self.resource.upper(),
+ help=help_str % self.resource)
+ return parser
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ obj_deleter = getattr(neutron_client,
+ "delete_%s" % self.resource)
+ if self.allow_names:
+ _id = find_resourceid_by_name_or_id(neutron_client, self.resource,
+ parsed_args.id)
+ else:
+ _id = parsed_args.id
+ obj_deleter(_id)
+ print >>self.app.stdout, (_('Deleted %(resource)s: %(id)s')
+ % {'id': parsed_args.id,
+ 'resource': self.resource})
+ return
+
+
+class ListCommand(NeutronCommand, lister.Lister):
+ """List resources that belong to a given tenant
+
+ """
+
+ api = 'network'
+ resource = None
+ log = None
+ _formatters = {}
+ list_columns = []
+ unknown_parts_flag = True
+ pagination_support = False
+ sorting_support = False
+
+ def get_parser(self, prog_name):
+ parser = super(ListCommand, self).get_parser(prog_name)
+ add_show_list_common_argument(parser)
+ if self.pagination_support:
+ add_pagination_argument(parser)
+ if self.sorting_support:
+ add_sorting_argument(parser)
+ return parser
+
+ def args2search_opts(self, parsed_args):
+ search_opts = {}
+ fields = parsed_args.fields
+ if parsed_args.fields:
+ search_opts.update({'fields': fields})
+ if parsed_args.show_details:
+ search_opts.update({'verbose': 'True'})
+ return search_opts
+
+ def call_server(self, neutron_client, search_opts, parsed_args):
+ obj_lister = getattr(neutron_client,
+ "list_%ss" % self.resource)
+ data = obj_lister(**search_opts)
+ return data
+
+ def retrieve_list(self, parsed_args):
+ """Retrieve a list of resources from Neutron server"""
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ _extra_values = parse_args_to_dict(self.values_specs)
+ _merge_args(self, parsed_args, _extra_values,
+ self.values_specs)
+ search_opts = self.args2search_opts(parsed_args)
+ search_opts.update(_extra_values)
+ if self.pagination_support:
+ page_size = parsed_args.page_size
+ if page_size:
+ search_opts.update({'limit': page_size})
+ if self.sorting_support:
+ keys = parsed_args.sort_key
+ if keys:
+ search_opts.update({'sort_key': keys})
+ dirs = parsed_args.sort_dir
+ len_diff = len(keys) - len(dirs)
+ if len_diff > 0:
+ dirs += ['asc'] * len_diff
+ elif len_diff < 0:
+ dirs = dirs[:len(keys)]
+ if dirs:
+ search_opts.update({'sort_dir': dirs})
+ data = self.call_server(neutron_client, search_opts, parsed_args)
+ collection = self.resource + "s"
+ return data.get(collection, [])
+
+ def extend_list(self, data, parsed_args):
+ """Update a retrieved list.
+
+ This method provides a way to modify a original list returned from
+ the neutron server. For example, you can add subnet cidr information
+ to a list network.
+ """
+ pass
+
+ def setup_columns(self, info, parsed_args):
+ _columns = len(info) > 0 and sorted(info[0].keys()) or []
+ if not _columns:
+ # clean the parsed_args.columns so that cliff will not break
+ parsed_args.columns = []
+ elif parsed_args.columns:
+ _columns = [x for x in parsed_args.columns if x in _columns]
+ elif self.list_columns:
+ # if no -c(s) by user and list_columns, we use columns in
+ # both list_columns and returned resource.
+ # Also Keep their order the same as in list_columns
+ _columns = [x for x in self.list_columns if x in _columns]
+ return (_columns, (utils.get_item_properties(
+ s, _columns, formatters=self._formatters, )
+ for s in info), )
+
+ def get_data(self, parsed_args):
+ self.log.debug('get_data(%s)' % parsed_args)
+ data = self.retrieve_list(parsed_args)
+ self.extend_list(data, parsed_args)
+ return self.setup_columns(data, parsed_args)
+
+
+class ShowCommand(NeutronCommand, show.ShowOne):
+ """Show information of a given resource
+
+ """
+
+ api = 'network'
+ resource = None
+ log = None
+ allow_names = True
+
+ def get_parser(self, prog_name):
+ parser = super(ShowCommand, self).get_parser(prog_name)
+ add_show_list_common_argument(parser)
+ if self.allow_names:
+ help_str = 'ID or name of %s to look up'
+ else:
+ help_str = 'ID of %s to look up'
+ parser.add_argument(
+ 'id', metavar=self.resource.upper(),
+ help=help_str % self.resource)
+ return parser
+
+ def get_data(self, parsed_args):
+ self.log.debug('get_data(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+
+ params = {}
+ if parsed_args.show_details:
+ params = {'verbose': 'True'}
+ if parsed_args.fields:
+ params = {'fields': parsed_args.fields}
+ if self.allow_names:
+ _id = find_resourceid_by_name_or_id(neutron_client, self.resource,
+ parsed_args.id)
+ else:
+ _id = parsed_args.id
+
+ obj_shower = getattr(neutron_client, "show_%s" % self.resource)
+ data = obj_shower(_id, **params)
+ self.format_output_data(data)
+ resource = data[self.resource]
+ if self.resource in data:
+ return zip(*sorted(resource.iteritems()))
+ else:
+ return None
diff --git a/neutronclient/neutron/v2_0/agent.py b/neutronclient/neutron/v2_0/agent.py
new file mode 100644
index 0000000..8c85f54
--- /dev/null
+++ b/neutronclient/neutron/v2_0/agent.py
@@ -0,0 +1,65 @@
+# Copyright 2013 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import logging
+
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+def _format_timestamp(component):
+ try:
+ return component['heartbeat_timestamp'].split(".", 2)[0]
+ except Exception:
+ return ''
+
+
+class ListAgent(neutronV20.ListCommand):
+ """List agents."""
+
+ resource = 'agent'
+ log = logging.getLogger(__name__ + '.ListAgent')
+ list_columns = ['id', 'agent_type', 'host', 'alive', 'admin_state_up']
+ _formatters = {'heartbeat_timestamp': _format_timestamp}
+
+ def extend_list(self, data, parsed_args):
+ for agent in data:
+ agent['alive'] = ":-)" if agent['alive'] else 'xxx'
+
+
+class ShowAgent(neutronV20.ShowCommand):
+ """Show information of a given agent."""
+
+ resource = 'agent'
+ log = logging.getLogger(__name__ + '.ShowAgent')
+ allow_names = False
+ json_indent = 5
+
+
+class DeleteAgent(neutronV20.DeleteCommand):
+ """Delete a given agent."""
+
+ log = logging.getLogger(__name__ + '.DeleteAgent')
+ resource = 'agent'
+ allow_names = False
+
+
+class UpdateAgent(neutronV20.UpdateCommand):
+ """Update a given agent."""
+
+ log = logging.getLogger(__name__ + '.UpdateAgent')
+ resource = 'agent'
+ allow_names = False
diff --git a/neutronclient/neutron/v2_0/agentscheduler.py b/neutronclient/neutron/v2_0/agentscheduler.py
new file mode 100644
index 0000000..d761125
--- /dev/null
+++ b/neutronclient/neutron/v2_0/agentscheduler.py
@@ -0,0 +1,234 @@
+# Copyright 2013 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import logging
+
+from neutronclient.neutron import v2_0 as neutronV20
+from neutronclient.neutron.v2_0 import network
+from neutronclient.neutron.v2_0 import router
+from neutronclient.openstack.common.gettextutils import _
+PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f"
+
+
+class AddNetworkToDhcpAgent(neutronV20.NeutronCommand):
+ """Add a network to a DHCP agent."""
+
+ log = logging.getLogger(__name__ + '.AddNetworkToDhcpAgent')
+
+ def get_parser(self, prog_name):
+ parser = super(AddNetworkToDhcpAgent, self).get_parser(prog_name)
+ parser.add_argument(
+ 'dhcp_agent',
+ help='ID of the DHCP agent')
+ parser.add_argument(
+ 'network',
+ help='network to add')
+ return parser
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ _net_id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, 'network', parsed_args.network)
+ neutron_client.add_network_to_dhcp_agent(parsed_args.dhcp_agent,
+ {'network_id': _net_id})
+ print >>self.app.stdout, (
+ _('Added network %s to DHCP agent') % parsed_args.network)
+
+
+class RemoveNetworkFromDhcpAgent(neutronV20.NeutronCommand):
+ """Remove a network from a DHCP agent."""
+ log = logging.getLogger(__name__ + '.RemoveNetworkFromDhcpAgent')
+
+ def get_parser(self, prog_name):
+ parser = super(RemoveNetworkFromDhcpAgent, self).get_parser(prog_name)
+ parser.add_argument(
+ 'dhcp_agent',
+ help='ID of the DHCP agent')
+ parser.add_argument(
+ 'network',
+ help='network to remove')
+ return parser
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ _net_id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, 'network', parsed_args.network)
+ neutron_client.remove_network_from_dhcp_agent(
+ parsed_args.dhcp_agent, _net_id)
+ print >>self.app.stdout, (
+ _('Removed network %s to DHCP agent') % parsed_args.network)
+
+
+class ListNetworksOnDhcpAgent(network.ListNetwork):
+ """List the networks on a DHCP agent."""
+
+ log = logging.getLogger(__name__ + '.ListNetworksOnDhcpAgent')
+ unknown_parts_flag = False
+
+ def get_parser(self, prog_name):
+ parser = super(ListNetworksOnDhcpAgent,
+ self).get_parser(prog_name)
+ parser.add_argument(
+ 'dhcp_agent',
+ help='ID of the DHCP agent')
+ return parser
+
+ def call_server(self, neutron_client, search_opts, parsed_args):
+ data = neutron_client.list_networks_on_dhcp_agent(
+ parsed_args.dhcp_agent, **search_opts)
+ return data
+
+
+class ListDhcpAgentsHostingNetwork(neutronV20.ListCommand):
+ """List DHCP agents hosting a network."""
+
+ resource = 'agent'
+ _formatters = {}
+ log = logging.getLogger(__name__ + '.ListDhcpAgentsHostingNetwork')
+ list_columns = ['id', 'host', 'admin_state_up', 'alive']
+ unknown_parts_flag = False
+
+ def get_parser(self, prog_name):
+ parser = super(ListDhcpAgentsHostingNetwork,
+ self).get_parser(prog_name)
+ parser.add_argument(
+ 'network',
+ help='network to query')
+ 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):
+ _id = neutronV20.find_resourceid_by_name_or_id(neutron_client,
+ 'network',
+ parsed_args.network)
+ search_opts['network'] = _id
+ data = neutron_client.list_dhcp_agent_hosting_networks(**search_opts)
+ return data
+
+
+class AddRouterToL3Agent(neutronV20.NeutronCommand):
+ """Add a router to a L3 agent."""
+
+ log = logging.getLogger(__name__ + '.AddRouterToL3Agent')
+
+ def get_parser(self, prog_name):
+ parser = super(AddRouterToL3Agent, self).get_parser(prog_name)
+ parser.add_argument(
+ 'l3_agent',
+ help='ID of the L3 agent')
+ parser.add_argument(
+ 'router',
+ help='router to add')
+ return parser
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ _id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, 'router', parsed_args.router)
+ neutron_client.add_router_to_l3_agent(parsed_args.l3_agent,
+ {'router_id': _id})
+ print >>self.app.stdout, (
+ _('Added router %s to L3 agent') % parsed_args.router)
+
+
+class RemoveRouterFromL3Agent(neutronV20.NeutronCommand):
+ """Remove a router from a L3 agent."""
+
+ log = logging.getLogger(__name__ + '.RemoveRouterFromL3Agent')
+
+ def get_parser(self, prog_name):
+ parser = super(RemoveRouterFromL3Agent, self).get_parser(prog_name)
+ parser.add_argument(
+ 'l3_agent',
+ help='ID of the L3 agent')
+ parser.add_argument(
+ 'router',
+ help='router to remove')
+ return parser
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ _id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, 'router', parsed_args.router)
+ neutron_client.remove_router_from_l3_agent(
+ parsed_args.l3_agent, _id)
+ print >>self.app.stdout, (
+ _('Removed Router %s to L3 agent') % parsed_args.router)
+
+
+class ListRoutersOnL3Agent(neutronV20.ListCommand):
+ """List the routers on a L3 agent."""
+
+ log = logging.getLogger(__name__ + '.ListRoutersOnL3Agent')
+ _formatters = {'external_gateway_info':
+ router._format_external_gateway_info}
+ list_columns = ['id', 'name', 'external_gateway_info']
+ resource = 'router'
+ unknown_parts_flag = False
+
+ def get_parser(self, prog_name):
+ parser = super(ListRoutersOnL3Agent,
+ self).get_parser(prog_name)
+ parser.add_argument(
+ 'l3_agent',
+ help='ID of the L3 agent to query')
+ return parser
+
+ def call_server(self, neutron_client, search_opts, parsed_args):
+ data = neutron_client.list_routers_on_l3_agent(
+ parsed_args.l3_agent, **search_opts)
+ return data
+
+
+class ListL3AgentsHostingRouter(neutronV20.ListCommand):
+ """List L3 agents hosting a router."""
+
+ resource = 'agent'
+ _formatters = {}
+ log = logging.getLogger(__name__ + '.ListL3AgentsHostingRouter')
+ list_columns = ['id', 'host', 'admin_state_up', 'alive', 'default']
+ unknown_parts_flag = False
+
+ def get_parser(self, prog_name):
+ parser = super(ListL3AgentsHostingRouter,
+ self).get_parser(prog_name)
+ parser.add_argument('router',
+ help='router to query')
+ 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):
+ _id = neutronV20.find_resourceid_by_name_or_id(neutron_client,
+ 'router',
+ parsed_args.router)
+ search_opts['router'] = _id
+ data = neutron_client.list_l3_agent_hosting_routers(**search_opts)
+ return data
diff --git a/neutronclient/neutron/v2_0/extension.py b/neutronclient/neutron/v2_0/extension.py
new file mode 100644
index 0000000..b2a6411
--- /dev/null
+++ b/neutronclient/neutron/v2_0/extension.py
@@ -0,0 +1,44 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import logging
+
+from neutronclient.neutron import v2_0 as cmd_base
+
+
+class ListExt(cmd_base.ListCommand):
+ """List all extensions."""
+
+ resource = 'extension'
+ log = logging.getLogger(__name__ + '.ListExt')
+ list_columns = ['alias', 'name']
+
+
+class ShowExt(cmd_base.ShowCommand):
+ """Show information of a given resource."""
+
+ resource = "extension"
+ log = logging.getLogger(__name__ + '.ShowExt')
+ allow_names = False
+
+ def get_parser(self, prog_name):
+ parser = super(cmd_base.ShowCommand, self).get_parser(prog_name)
+ cmd_base.add_show_list_common_argument(parser)
+ parser.add_argument(
+ 'id', metavar='EXT-ALIAS',
+ help='the extension alias')
+ return parser
diff --git a/neutronclient/neutron/v2_0/floatingip.py b/neutronclient/neutron/v2_0/floatingip.py
new file mode 100644
index 0000000..7931f31
--- /dev/null
+++ b/neutronclient/neutron/v2_0/floatingip.py
@@ -0,0 +1,151 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import argparse
+import logging
+
+from neutronclient.neutron import v2_0 as neutronV20
+from neutronclient.openstack.common.gettextutils import _
+
+
+class ListFloatingIP(neutronV20.ListCommand):
+ """List floating ips that belong to a given tenant."""
+
+ resource = 'floatingip'
+ log = logging.getLogger(__name__ + '.ListFloatingIP')
+ list_columns = ['id', 'fixed_ip_address', 'floating_ip_address',
+ 'port_id']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowFloatingIP(neutronV20.ShowCommand):
+ """Show information of a given floating ip."""
+
+ resource = 'floatingip'
+ log = logging.getLogger(__name__ + '.ShowFloatingIP')
+ allow_names = False
+
+
+class CreateFloatingIP(neutronV20.CreateCommand):
+ """Create a floating ip for a given tenant."""
+
+ resource = 'floatingip'
+ log = logging.getLogger(__name__ + '.CreateFloatingIP')
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'floating_network_id', metavar='FLOATING_NETWORK',
+ help='Network name or id to allocate floating IP from')
+ parser.add_argument(
+ '--port-id',
+ help='ID of the port to be associated with the floatingip')
+ parser.add_argument(
+ '--port_id',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--fixed-ip-address',
+ help=('IP address on the port (only required if port has multiple'
+ 'IPs)'))
+ parser.add_argument(
+ '--fixed_ip_address',
+ help=argparse.SUPPRESS)
+
+ def args2body(self, parsed_args):
+ _network_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'network', parsed_args.floating_network_id)
+ body = {self.resource: {'floating_network_id': _network_id}}
+ if parsed_args.port_id:
+ body[self.resource].update({'port_id': parsed_args.port_id})
+ if parsed_args.tenant_id:
+ body[self.resource].update({'tenant_id': parsed_args.tenant_id})
+ if parsed_args.fixed_ip_address:
+ body[self.resource].update({'fixed_ip_address':
+ parsed_args.fixed_ip_address})
+ return body
+
+
+class DeleteFloatingIP(neutronV20.DeleteCommand):
+ """Delete a given floating ip."""
+
+ log = logging.getLogger(__name__ + '.DeleteFloatingIP')
+ resource = 'floatingip'
+ allow_names = False
+
+
+class AssociateFloatingIP(neutronV20.NeutronCommand):
+ """Create a mapping between a floating ip and a fixed ip."""
+
+ api = 'network'
+ log = logging.getLogger(__name__ + '.AssociateFloatingIP')
+ resource = 'floatingip'
+
+ def get_parser(self, prog_name):
+ parser = super(AssociateFloatingIP, self).get_parser(prog_name)
+ parser.add_argument(
+ 'floatingip_id', metavar='FLOATINGIP_ID',
+ help='ID of the floating IP to associate')
+ parser.add_argument(
+ 'port_id', metavar='PORT',
+ help='ID or name of the port to be associated with the floatingip')
+ parser.add_argument(
+ '--fixed-ip-address',
+ help=('IP address on the port (only required if port has multiple'
+ 'IPs)'))
+ parser.add_argument(
+ '--fixed_ip_address',
+ help=argparse.SUPPRESS)
+ return parser
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ update_dict = {}
+ if parsed_args.port_id:
+ update_dict['port_id'] = parsed_args.port_id
+ if parsed_args.fixed_ip_address:
+ update_dict['fixed_ip_address'] = parsed_args.fixed_ip_address
+ neutron_client.update_floatingip(parsed_args.floatingip_id,
+ {'floatingip': update_dict})
+ print >>self.app.stdout, (
+ _('Associated floatingip %s') % parsed_args.floatingip_id)
+
+
+class DisassociateFloatingIP(neutronV20.NeutronCommand):
+ """Remove a mapping from a floating ip to a fixed ip.
+ """
+
+ api = 'network'
+ log = logging.getLogger(__name__ + '.DisassociateFloatingIP')
+ resource = 'floatingip'
+
+ def get_parser(self, prog_name):
+ parser = super(DisassociateFloatingIP, self).get_parser(prog_name)
+ parser.add_argument(
+ 'floatingip_id', metavar='FLOATINGIP_ID',
+ help='ID of the floating IP to associate')
+ return parser
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ neutron_client.update_floatingip(parsed_args.floatingip_id,
+ {'floatingip': {'port_id': None}})
+ print >>self.app.stdout, (
+ _('Disassociated floatingip %s') % parsed_args.floatingip_id)
diff --git a/neutronclient/neutron/v2_0/lb/__init__.py b/neutronclient/neutron/v2_0/lb/__init__.py
new file mode 100644
index 0000000..1668497
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2013 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
diff --git a/neutronclient/neutron/v2_0/lb/healthmonitor.py b/neutronclient/neutron/v2_0/lb/healthmonitor.py
new file mode 100644
index 0000000..a6e847a
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/healthmonitor.py
@@ -0,0 +1,174 @@
+# Copyright 2013 Mirantis Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# @author: Ilya Shakhat, Mirantis Inc.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import logging
+
+from neutronclient.neutron import v2_0 as neutronV20
+from neutronclient.openstack.common.gettextutils import _
+
+
+class ListHealthMonitor(neutronV20.ListCommand):
+ """List healthmonitors that belong to a given tenant."""
+
+ resource = 'health_monitor'
+ log = logging.getLogger(__name__ + '.ListHealthMonitor')
+ list_columns = ['id', 'type', 'admin_state_up', 'status']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowHealthMonitor(neutronV20.ShowCommand):
+ """Show information of a given healthmonitor."""
+
+ resource = 'health_monitor'
+ log = logging.getLogger(__name__ + '.ShowHealthMonitor')
+
+
+class CreateHealthMonitor(neutronV20.CreateCommand):
+ """Create a healthmonitor."""
+
+ resource = 'health_monitor'
+ log = logging.getLogger(__name__ + '.CreateHealthMonitor')
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help='set admin state up to false')
+ parser.add_argument(
+ '--expected-codes',
+ help='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". ')
+ parser.add_argument(
+ '--http-method',
+ help='the HTTP method used for requests by the monitor of type '
+ 'HTTP.')
+ parser.add_argument(
+ '--url-path',
+ help='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)')
+ parser.add_argument(
+ '--delay',
+ required=True,
+ help='the minimum time in seconds between regular connections '
+ 'of the member.')
+ parser.add_argument(
+ '--max-retries',
+ required=True,
+ help='number of permissible connection failures before changing '
+ 'the member status to INACTIVE.')
+ parser.add_argument(
+ '--timeout',
+ required=True,
+ help='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.')
+ parser.add_argument(
+ '--type',
+ required=True,
+ help='one of predefined health monitor types, e.g. RoundRobin')
+
+ def args2body(self, parsed_args):
+ body = {
+ self.resource: {
+ 'admin_state_up': parsed_args.admin_state,
+ 'delay': parsed_args.delay,
+ 'max_retries': parsed_args.max_retries,
+ 'timeout': parsed_args.timeout,
+ 'type': parsed_args.type,
+ },
+ }
+ neutronV20.update_dict(parsed_args, body[self.resource],
+ ['expected_codes', 'http_method', 'url_path',
+ 'tenant_id'])
+ return body
+
+
+class UpdateHealthMonitor(neutronV20.UpdateCommand):
+ """Update a given healthmonitor."""
+
+ resource = 'health_monitor'
+ log = logging.getLogger(__name__ + '.UpdateHealthMonitor')
+
+
+class DeleteHealthMonitor(neutronV20.DeleteCommand):
+ """Delete a given healthmonitor."""
+
+ resource = 'health_monitor'
+ log = logging.getLogger(__name__ + '.DeleteHealthMonitor')
+
+
+class AssociateHealthMonitor(neutronV20.NeutronCommand):
+ """Create a mapping between a health monitor and a pool."""
+
+ log = logging.getLogger(__name__ + '.AssociateHealthMonitor')
+ resource = 'health_monitor'
+
+ def get_parser(self, prog_name):
+ parser = super(AssociateHealthMonitor, self).get_parser(prog_name)
+ parser.add_argument(
+ 'health_monitor_id',
+ help='Health monitor to associate')
+ parser.add_argument(
+ 'pool_id',
+ help='ID of the pool to be associated with the health monitor')
+ return parser
+
+ def run(self, parsed_args):
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ body = {'health_monitor': {'id': parsed_args.health_monitor_id}}
+ pool_id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, 'pool', parsed_args.pool_id)
+ neutron_client.associate_health_monitor(pool_id, body)
+ print >>self.app.stdout, (_('Associated health monitor '
+ '%s') % parsed_args.health_monitor_id)
+
+
+class DisassociateHealthMonitor(neutronV20.NeutronCommand):
+ """Remove a mapping from a health monitor to a pool."""
+
+ log = logging.getLogger(__name__ + '.DisassociateHealthMonitor')
+ resource = 'health_monitor'
+
+ def get_parser(self, prog_name):
+ parser = super(DisassociateHealthMonitor, self).get_parser(prog_name)
+ parser.add_argument(
+ 'health_monitor_id',
+ help='Health monitor to associate')
+ parser.add_argument(
+ 'pool_id',
+ help='ID of the pool to be associated with the health monitor')
+ return parser
+
+ def run(self, parsed_args):
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ pool_id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, 'pool', parsed_args.pool_id)
+ neutron_client.disassociate_health_monitor(pool_id,
+ parsed_args
+ .health_monitor_id)
+ print >>self.app.stdout, (_('Disassociated health monitor '
+ '%s') % parsed_args.health_monitor_id)
diff --git a/neutronclient/neutron/v2_0/lb/member.py b/neutronclient/neutron/v2_0/lb/member.py
new file mode 100644
index 0000000..b13fdcd
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/member.py
@@ -0,0 +1,99 @@
+# Copyright 2013 Mirantis Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# @author: Ilya Shakhat, Mirantis Inc.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import logging
+
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+class ListMember(neutronV20.ListCommand):
+ """List members that belong to a given tenant."""
+
+ resource = 'member'
+ log = logging.getLogger(__name__ + '.ListMember')
+ list_columns = [
+ 'id', 'address', 'protocol_port', 'admin_state_up', 'status'
+ ]
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowMember(neutronV20.ShowCommand):
+ """Show information of a given member."""
+
+ resource = 'member'
+ log = logging.getLogger(__name__ + '.ShowMember')
+
+
+class CreateMember(neutronV20.CreateCommand):
+ """Create a member."""
+
+ resource = 'member'
+ log = logging.getLogger(__name__ + '.CreateMember')
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'pool_id', metavar='pool',
+ help='Pool id or name this vip belongs to')
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help='set admin state up to false')
+ parser.add_argument(
+ '--weight',
+ help='weight of pool member in the pool')
+ parser.add_argument(
+ '--address',
+ required=True,
+ help='IP address of the pool member on the pool network. ')
+ parser.add_argument(
+ '--protocol-port',
+ required=True,
+ help='port on which the pool member listens for requests or '
+ 'connections. ')
+
+ def args2body(self, parsed_args):
+ _pool_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'pool', parsed_args.pool_id)
+ body = {
+ self.resource: {
+ 'pool_id': _pool_id,
+ 'admin_state_up': parsed_args.admin_state,
+ },
+ }
+ neutronV20.update_dict(
+ parsed_args,
+ body[self.resource],
+ ['address', 'protocol_port', 'weight', 'tenant_id']
+ )
+ return body
+
+
+class UpdateMember(neutronV20.UpdateCommand):
+ """Update a given member."""
+
+ resource = 'member'
+ log = logging.getLogger(__name__ + '.UpdateMember')
+
+
+class DeleteMember(neutronV20.DeleteCommand):
+ """Delete a given member."""
+
+ resource = 'member'
+ log = logging.getLogger(__name__ + '.DeleteMember')
diff --git a/neutronclient/neutron/v2_0/lb/pool.py b/neutronclient/neutron/v2_0/lb/pool.py
new file mode 100644
index 0000000..4fb1e79
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/pool.py
@@ -0,0 +1,124 @@
+# Copyright 2013 Mirantis Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# @author: Ilya Shakhat, Mirantis Inc.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import logging
+
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+class ListPool(neutronV20.ListCommand):
+ """List pools that belong to a given tenant."""
+
+ resource = 'pool'
+ log = logging.getLogger(__name__ + '.ListPool')
+ list_columns = ['id', 'name', 'lb_method', 'protocol',
+ 'admin_state_up', 'status']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowPool(neutronV20.ShowCommand):
+ """Show information of a given pool."""
+
+ resource = 'pool'
+ log = logging.getLogger(__name__ + '.ShowPool')
+
+
+class CreatePool(neutronV20.CreateCommand):
+ """Create a pool."""
+
+ resource = 'pool'
+ log = logging.getLogger(__name__ + '.CreatePool')
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help='set admin state up to false')
+ parser.add_argument(
+ '--description',
+ help='description of the pool')
+ parser.add_argument(
+ '--lb-method',
+ required=True,
+ help='the algorithm used to distribute load between the members '
+ 'of the pool')
+ parser.add_argument(
+ '--name',
+ required=True,
+ help='the name of the pool')
+ parser.add_argument(
+ '--protocol',
+ required=True,
+ help='protocol for balancing')
+ parser.add_argument(
+ '--subnet-id',
+ required=True,
+ help='the subnet on which the members of the pool will be located')
+
+ def args2body(self, parsed_args):
+ _subnet_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'subnet', parsed_args.subnet_id)
+ body = {
+ self.resource: {
+ 'admin_state_up': parsed_args.admin_state,
+ 'subnet_id': _subnet_id,
+ },
+ }
+ neutronV20.update_dict(parsed_args, body[self.resource],
+ ['description', 'lb_method', 'name',
+ 'protocol', 'tenant_id'])
+ return body
+
+
+class UpdatePool(neutronV20.UpdateCommand):
+ """Update a given pool."""
+
+ resource = 'pool'
+ log = logging.getLogger(__name__ + '.UpdatePool')
+
+
+class DeletePool(neutronV20.DeleteCommand):
+ """Delete a given pool."""
+
+ resource = 'pool'
+ log = logging.getLogger(__name__ + '.DeletePool')
+
+
+class RetrievePoolStats(neutronV20.ShowCommand):
+ """Retrieve stats for a given pool."""
+
+ resource = 'pool'
+ log = logging.getLogger(__name__ + '.RetrievePoolStats')
+
+ def get_data(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ params = {}
+ if parsed_args.fields:
+ params = {'fields': parsed_args.fields}
+
+ data = neutron_client.retrieve_pool_stats(parsed_args.id, **params)
+ self.format_output_data(data)
+ stats = data['stats']
+ if 'stats' in data:
+ return zip(*sorted(stats.iteritems()))
+ else:
+ return None
diff --git a/neutronclient/neutron/v2_0/lb/vip.py b/neutronclient/neutron/v2_0/lb/vip.py
new file mode 100644
index 0000000..f836862
--- /dev/null
+++ b/neutronclient/neutron/v2_0/lb/vip.py
@@ -0,0 +1,115 @@
+# Copyright 2013 Mirantis Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# @author: Ilya Shakhat, Mirantis Inc.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import logging
+
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+class ListVip(neutronV20.ListCommand):
+ """List vips that belong to a given tenant."""
+
+ resource = 'vip'
+ log = logging.getLogger(__name__ + '.ListVip')
+ list_columns = ['id', 'name', 'algorithm', 'address', 'protocol',
+ 'admin_state_up', 'status']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowVip(neutronV20.ShowCommand):
+ """Show information of a given vip."""
+
+ resource = 'vip'
+ log = logging.getLogger(__name__ + '.ShowVip')
+
+
+class CreateVip(neutronV20.CreateCommand):
+ """Create a vip."""
+
+ resource = 'vip'
+ log = logging.getLogger(__name__ + '.CreateVip')
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'pool_id', metavar='pool',
+ help='Pool id or name this vip belongs to')
+ parser.add_argument(
+ '--address',
+ help='IP address of the vip')
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help='set admin state up to false')
+ parser.add_argument(
+ '--connection-limit',
+ help='the maximum number of connections per second allowed for '
+ 'the vip')
+ parser.add_argument(
+ '--description',
+ help='description of the vip')
+ parser.add_argument(
+ '--name',
+ required=True,
+ help='name of the vip')
+ parser.add_argument(
+ '--protocol-port',
+ required=True,
+ help='TCP port on which to listen for client traffic that is '
+ 'associated with the vip address')
+ parser.add_argument(
+ '--protocol',
+ required=True,
+ help='protocol for balancing')
+ parser.add_argument(
+ '--subnet-id',
+ required=True,
+ help='the subnet on which to allocate the vip address')
+
+ def args2body(self, parsed_args):
+ _pool_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'pool', parsed_args.pool_id)
+ _subnet_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'subnet', parsed_args.subnet_id)
+ body = {
+ self.resource: {
+ 'pool_id': _pool_id,
+ 'admin_state_up': parsed_args.admin_state,
+ 'subnet_id': _subnet_id,
+ },
+ }
+ neutronV20.update_dict(parsed_args, body[self.resource],
+ ['address', 'connection_limit', 'description',
+ 'name', 'protocol_port', 'protocol',
+ 'tenant_id'])
+ return body
+
+
+class UpdateVip(neutronV20.UpdateCommand):
+ """Update a given vip."""
+
+ resource = 'vip'
+ log = logging.getLogger(__name__ + '.UpdateVip')
+
+
+class DeleteVip(neutronV20.DeleteCommand):
+ """Delete a given vip."""
+
+ resource = 'vip'
+ log = logging.getLogger(__name__ + '.DeleteVip')
diff --git a/neutronclient/neutron/v2_0/network.py b/neutronclient/neutron/v2_0/network.py
new file mode 100644
index 0000000..0f384ab
--- /dev/null
+++ b/neutronclient/neutron/v2_0/network.py
@@ -0,0 +1,152 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import argparse
+import logging
+
+from neutronclient.common import exceptions
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+def _format_subnets(network):
+ try:
+ return '\n'.join([' '.join([s['id'], s.get('cidr', '')])
+ for s in network['subnets']])
+ except Exception:
+ return ''
+
+
+class ListNetwork(neutronV20.ListCommand):
+ """List networks that belong to a given tenant."""
+
+ # Length of a query filter on subnet id
+ # id=<uuid>& (with len(uuid)=36)
+ subnet_id_filter_len = 40
+ resource = 'network'
+ log = logging.getLogger(__name__ + '.ListNetwork')
+ _formatters = {'subnets': _format_subnets, }
+ list_columns = ['id', 'name', 'subnets']
+ pagination_support = True
+ sorting_support = True
+
+ def extend_list(self, data, parsed_args):
+ """Add subnet information to a network list."""
+ neutron_client = self.get_client()
+ search_opts = {'fields': ['id', 'cidr']}
+ if self.pagination_support:
+ page_size = parsed_args.page_size
+ if page_size:
+ search_opts.update({'limit': page_size})
+ subnet_ids = []
+ for n in data:
+ if 'subnets' in n:
+ subnet_ids.extend(n['subnets'])
+
+ def _get_subnet_list(sub_ids):
+ search_opts['id'] = sub_ids
+ return neutron_client.list_subnets(
+ **search_opts).get('subnets', [])
+
+ try:
+ subnets = _get_subnet_list(subnet_ids)
+ except exceptions.RequestURITooLong as uri_len_exc:
+ # The URI is too long because of too many subnet_id filters
+ # Use the excess attribute of the exception to know how many
+ # subnet_id filters can be inserted into a single request
+ subnet_count = len(subnet_ids)
+ max_size = ((self.subnet_id_filter_len * subnet_count) -
+ uri_len_exc.excess)
+ chunk_size = max_size / self.subnet_id_filter_len
+ subnets = []
+ for i in xrange(0, subnet_count, chunk_size):
+ subnets.extend(
+ _get_subnet_list(subnet_ids[i: i + chunk_size]))
+
+ subnet_dict = dict([(s['id'], s) for s in subnets])
+ for n in data:
+ if 'subnets' in n:
+ n['subnets'] = [(subnet_dict.get(s) or {"id": s})
+ for s in n['subnets']]
+
+
+class ListExternalNetwork(ListNetwork):
+ """List external networks that belong to a given tenant."""
+
+ log = logging.getLogger(__name__ + '.ListExternalNetwork')
+ pagination_support = True
+ sorting_support = True
+
+ def retrieve_list(self, parsed_args):
+ external = '--router:external=True'
+ if external not in self.values_specs:
+ self.values_specs.append('--router:external=True')
+ return super(ListExternalNetwork, self).retrieve_list(parsed_args)
+
+
+class ShowNetwork(neutronV20.ShowCommand):
+ """Show information of a given network."""
+
+ resource = 'network'
+ log = logging.getLogger(__name__ + '.ShowNetwork')
+
+
+class CreateNetwork(neutronV20.CreateCommand):
+ """Create a network for a given tenant."""
+
+ resource = 'network'
+ log = logging.getLogger(__name__ + '.CreateNetwork')
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help='Set Admin State Up to false')
+ parser.add_argument(
+ '--admin_state_down',
+ dest='admin_state', action='store_false',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--shared',
+ action='store_true',
+ help='Set the network as shared')
+ parser.add_argument(
+ 'name', metavar='NAME',
+ help='Name of network to create')
+
+ def args2body(self, parsed_args):
+ body = {'network': {
+ 'name': parsed_args.name,
+ 'admin_state_up': parsed_args.admin_state}, }
+ if parsed_args.tenant_id:
+ body['network'].update({'tenant_id': parsed_args.tenant_id})
+ if parsed_args.shared:
+ body['network'].update({'shared': parsed_args.shared})
+ return body
+
+
+class DeleteNetwork(neutronV20.DeleteCommand):
+ """Delete a given network."""
+
+ log = logging.getLogger(__name__ + '.DeleteNetwork')
+ resource = 'network'
+
+
+class UpdateNetwork(neutronV20.UpdateCommand):
+ """Update network's information."""
+
+ log = logging.getLogger(__name__ + '.UpdateNetwork')
+ resource = 'network'
diff --git a/neutronclient/neutron/v2_0/nvp_qos_queue.py b/neutronclient/neutron/v2_0/nvp_qos_queue.py
new file mode 100644
index 0000000..79a1801
--- /dev/null
+++ b/neutronclient/neutron/v2_0/nvp_qos_queue.py
@@ -0,0 +1,89 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Nicira Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+class ListQoSQueue(neutronV20.ListCommand):
+ """List queues that belong to a given tenant."""
+
+ resource = 'qos_queue'
+ log = logging.getLogger(__name__ + '.ListQoSQueue')
+ list_columns = ['id', 'name', 'min', 'max',
+ 'qos_marking', 'dscp', 'default']
+
+
+class ShowQoSQueue(neutronV20.ShowCommand):
+ """Show information of a given queue."""
+
+ resource = 'qos_queue'
+ log = logging.getLogger(__name__ + '.ShowQoSQueue')
+ allow_names = True
+
+
+class CreateQoSQueue(neutronV20.CreateCommand):
+ """Create a queue."""
+
+ resource = 'qos_queue'
+ log = logging.getLogger(__name__ + '.CreateQoSQueue')
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'name', metavar='NAME',
+ help='Name of queue')
+ parser.add_argument(
+ '--min',
+ help='min-rate'),
+ parser.add_argument(
+ '--max',
+ help='max-rate'),
+ parser.add_argument(
+ '--qos-marking',
+ help='qos marking untrusted/trusted'),
+ parser.add_argument(
+ '--default',
+ default=False,
+ help=('If true all ports created with be the size of this queue'
+ ' if queue is not specified')),
+ parser.add_argument(
+ '--dscp',
+ help='Differentiated Services Code Point'),
+
+ def args2body(self, parsed_args):
+ params = {'name': parsed_args.name,
+ 'default': parsed_args.default}
+ if parsed_args.min:
+ params['min'] = parsed_args.min
+ if parsed_args.max:
+ params['max'] = parsed_args.max
+ if parsed_args.qos_marking:
+ params['qos_marking'] = parsed_args.qos_marking
+ if parsed_args.dscp:
+ params['dscp'] = parsed_args.dscp
+ if parsed_args.tenant_id:
+ params['tenant_id'] = parsed_args.tenant_id
+ return {'qos_queue': params}
+
+
+class DeleteQoSQueue(neutronV20.DeleteCommand):
+ """Delete a given queue."""
+
+ log = logging.getLogger(__name__ + '.DeleteQoSQueue')
+ resource = 'qos_queue'
+ allow_names = True
diff --git a/neutronclient/neutron/v2_0/nvpnetworkgateway.py b/neutronclient/neutron/v2_0/nvpnetworkgateway.py
new file mode 100644
index 0000000..1853514
--- /dev/null
+++ b/neutronclient/neutron/v2_0/nvpnetworkgateway.py
@@ -0,0 +1,159 @@
+# Copyright 2013 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import logging
+
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronV20
+from neutronclient.openstack.common.gettextutils import _
+
+RESOURCE = 'network_gateway'
+
+
+class ListNetworkGateway(neutronV20.ListCommand):
+ """List network gateways for a given tenant."""
+
+ resource = RESOURCE
+ log = logging.getLogger(__name__ + '.ListNetworkGateway')
+ list_columns = ['id', 'name']
+
+
+class ShowNetworkGateway(neutronV20.ShowCommand):
+ """Show information of a given network gateway."""
+
+ resource = RESOURCE
+ log = logging.getLogger(__name__ + '.ShowNetworkGateway')
+
+
+class CreateNetworkGateway(neutronV20.CreateCommand):
+ """Create a network gateway."""
+
+ resource = RESOURCE
+ log = logging.getLogger(__name__ + '.CreateNetworkGateway')
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'name', metavar='NAME',
+ help='Name of network gateway to create')
+ parser.add_argument(
+ '--device',
+ action='append',
+ help='device info for this gateway '
+ 'device_id=<device identifier>,'
+ 'interface_name=<name_or_identifier> '
+ 'It can be repeated for multiple devices for HA gateways')
+
+ def args2body(self, parsed_args):
+ body = {self.resource: {
+ 'name': parsed_args.name}}
+ devices = []
+ if parsed_args.device:
+ for device in parsed_args.device:
+ devices.append(utils.str2dict(device))
+ if devices:
+ body[self.resource].update({'devices': devices})
+ if parsed_args.tenant_id:
+ body[self.resource].update({'tenant_id': parsed_args.tenant_id})
+ return body
+
+
+class DeleteNetworkGateway(neutronV20.DeleteCommand):
+ """Delete a given network gateway."""
+
+ resource = RESOURCE
+ log = logging.getLogger(__name__ + '.DeleteNetworkGateway')
+
+
+class UpdateNetworkGateway(neutronV20.UpdateCommand):
+ """Update the name for a network gateway."""
+
+ resource = RESOURCE
+ log = logging.getLogger(__name__ + '.UpdateNetworkGateway')
+
+
+class NetworkGatewayInterfaceCommand(neutronV20.NeutronCommand):
+ """Base class for connecting/disconnecting networks to/from a gateway."""
+
+ resource = RESOURCE
+
+ def get_parser(self, prog_name):
+ parser = super(NetworkGatewayInterfaceCommand,
+ self).get_parser(prog_name)
+ parser.add_argument(
+ 'net_gateway_id', metavar='NET-GATEWAY-ID',
+ help='ID of the network gateway')
+ parser.add_argument(
+ 'network_id', metavar='NETWORK-ID',
+ help='ID of the internal network to connect on the gateway')
+ parser.add_argument(
+ '--segmentation-type',
+ help=('L2 segmentation strategy on the external side of '
+ 'the gateway (e.g.: VLAN, FLAT)'))
+ parser.add_argument(
+ '--segmentation-id',
+ help=('Identifier for the L2 segment on the external side '
+ 'of the gateway'))
+ return parser
+
+ def retrieve_ids(self, client, args):
+ gateway_id = neutronV20.find_resourceid_by_name_or_id(
+ client, self.resource, args.net_gateway_id)
+ network_id = neutronV20.find_resourceid_by_name_or_id(
+ client, 'network', args.network_id)
+ return (gateway_id, network_id)
+
+
+class ConnectNetworkGateway(NetworkGatewayInterfaceCommand):
+ """Add an internal network interface to a router."""
+
+ log = logging.getLogger(__name__ + '.ConnectNetworkGateway')
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ (gateway_id, network_id) = self.retrieve_ids(neutron_client,
+ parsed_args)
+ neutron_client.connect_network_gateway(
+ gateway_id, {'network_id': network_id,
+ 'segmentation_type': parsed_args.segmentation_type,
+ 'segmentation_id': parsed_args.segmentation_id})
+ # TODO(Salvatore-Orlando): Do output formatting as
+ # any other command
+ print >>self.app.stdout, (
+ _('Connected network to gateway %s') % gateway_id)
+
+
+class DisconnectNetworkGateway(NetworkGatewayInterfaceCommand):
+ """Remove a network from a network gateway."""
+
+ log = logging.getLogger(__name__ + '.DisconnectNetworkGateway')
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ (gateway_id, network_id) = self.retrieve_ids(neutron_client,
+ parsed_args)
+ neutron_client.disconnect_network_gateway(
+ gateway_id, {'network_id': network_id,
+ 'segmentation_type': parsed_args.segmentation_type,
+ 'segmentation_id': parsed_args.segmentation_id})
+ # TODO(Salvatore-Orlando): Do output formatting as
+ # any other command
+ print >>self.app.stdout, (
+ _('Disconnected network from gateway %s') % gateway_id)
diff --git a/neutronclient/neutron/v2_0/port.py b/neutronclient/neutron/v2_0/port.py
new file mode 100644
index 0000000..f8ac9a9
--- /dev/null
+++ b/neutronclient/neutron/v2_0/port.py
@@ -0,0 +1,184 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import argparse
+import logging
+
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+def _format_fixed_ips(port):
+ try:
+ return '\n'.join([utils.dumps(ip) for ip in port['fixed_ips']])
+ except Exception:
+ return ''
+
+
+class ListPort(neutronV20.ListCommand):
+ """List ports that belong to a given tenant."""
+
+ resource = 'port'
+ log = logging.getLogger(__name__ + '.ListPort')
+ _formatters = {'fixed_ips': _format_fixed_ips, }
+ list_columns = ['id', 'name', 'mac_address', 'fixed_ips']
+ pagination_support = True
+ sorting_support = True
+
+
+class ListRouterPort(neutronV20.ListCommand):
+ """List ports that belong to a given tenant, with specified router."""
+
+ resource = 'port'
+ log = logging.getLogger(__name__ + '.ListRouterPort')
+ _formatters = {'fixed_ips': _format_fixed_ips, }
+ list_columns = ['id', 'name', 'mac_address', 'fixed_ips']
+ pagination_support = True
+ sorting_support = True
+
+ def get_parser(self, prog_name):
+ parser = super(ListRouterPort, self).get_parser(prog_name)
+ parser.add_argument(
+ 'id', metavar='router',
+ help='ID or name of router to look up')
+ return parser
+
+ def get_data(self, parsed_args):
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ _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)
+
+
+class ShowPort(neutronV20.ShowCommand):
+ """Show information of a given port."""
+
+ resource = 'port'
+ log = logging.getLogger(__name__ + '.ShowPort')
+
+
+class CreatePort(neutronV20.CreateCommand):
+ """Create a port for a given tenant."""
+
+ resource = 'port'
+ log = logging.getLogger(__name__ + '.CreatePort')
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--name',
+ help='name of this port')
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help='set admin state up to false')
+ parser.add_argument(
+ '--admin_state_down',
+ dest='admin_state', action='store_false',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--mac-address',
+ help='mac address of this port')
+ parser.add_argument(
+ '--mac_address',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--device-id',
+ help='device id of this port')
+ parser.add_argument(
+ '--device_id',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--fixed-ip', metavar='ip_address=IP_ADDR',
+ action='append',
+ help='desired IP for this port: '
+ 'subnet_id=<name_or_id>,ip_address=<ip>, '
+ '(This option can be repeated.)')
+ parser.add_argument(
+ '--fixed_ip',
+ action='append',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--security-group', metavar='SECURITY_GROUP',
+ default=[], action='append', dest='security_groups',
+ help='security group associated with the port '
+ '(This option can be repeated)')
+ parser.add_argument(
+ 'network_id', metavar='NETWORK',
+ help='Network id or name this port belongs to')
+
+ def args2body(self, parsed_args):
+ _network_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'network', parsed_args.network_id)
+ body = {'port': {'admin_state_up': parsed_args.admin_state,
+ 'network_id': _network_id, }, }
+ if parsed_args.mac_address:
+ body['port'].update({'mac_address': parsed_args.mac_address})
+ if parsed_args.device_id:
+ body['port'].update({'device_id': parsed_args.device_id})
+ if parsed_args.tenant_id:
+ body['port'].update({'tenant_id': parsed_args.tenant_id})
+ if parsed_args.name:
+ body['port'].update({'name': parsed_args.name})
+ 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']
+ _subnet_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'subnet', subnet_name_id)
+ ip_dict['subnet_id'] = _subnet_id
+ ips.append(ip_dict)
+ if ips:
+ body['port'].update({'fixed_ips': ips})
+
+ _sgids = []
+ for sg in parsed_args.security_groups:
+ _sgids.append(neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'security_group', sg))
+ if _sgids:
+ body['port']['security_groups'] = _sgids
+
+ return body
+
+
+class DeletePort(neutronV20.DeleteCommand):
+ """Delete a given port."""
+
+ resource = 'port'
+ log = logging.getLogger(__name__ + '.DeletePort')
+
+
+class UpdatePort(neutronV20.UpdateCommand):
+ """Update port's information."""
+
+ resource = 'port'
+ log = logging.getLogger(__name__ + '.UpdatePort')
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--no-security-groups',
+ action='store_true',
+ help='remove security groups from port')
+
+ def args2body(self, parsed_args):
+ body = {'port': {}}
+ if parsed_args.no_security_groups:
+ body['port'].update({'security_groups': None})
+ return body
diff --git a/neutronclient/neutron/v2_0/quota.py b/neutronclient/neutron/v2_0/quota.py
new file mode 100644
index 0000000..bc568ef
--- /dev/null
+++ b/neutronclient/neutron/v2_0/quota.py
@@ -0,0 +1,232 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import argparse
+import logging
+
+from cliff import lister
+from cliff import show
+
+from neutronclient.common import exceptions
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronV20
+from neutronclient.openstack.common.gettextutils import _
+
+
+def get_tenant_id(tenant_id, client):
+ return (tenant_id if tenant_id else
+ client.get_quotas_tenant()['tenant']['tenant_id'])
+
+
+class DeleteQuota(neutronV20.NeutronCommand):
+ """Delete defined quotas of a given tenant."""
+
+ api = 'network'
+ resource = 'quota'
+ log = logging.getLogger(__name__ + '.DeleteQuota')
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteQuota, self).get_parser(prog_name)
+ parser.add_argument(
+ '--tenant-id', metavar='tenant-id',
+ help='the owner tenant ID')
+ parser.add_argument(
+ '--tenant_id',
+ help=argparse.SUPPRESS)
+ return parser
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ tenant_id = get_tenant_id(parsed_args.tenant_id,
+ neutron_client)
+ obj_deleter = getattr(neutron_client,
+ "delete_%s" % self.resource)
+ obj_deleter(tenant_id)
+ print >>self.app.stdout, (_('Deleted %(resource)s: %(tenant_id)s')
+ % {'tenant_id': tenant_id,
+ 'resource': self.resource})
+ return
+
+
+class ListQuota(neutronV20.NeutronCommand, lister.Lister):
+ """List defined quotas of all tenants."""
+
+ api = 'network'
+ resource = 'quota'
+ log = logging.getLogger(__name__ + '.ListQuota')
+
+ def get_parser(self, prog_name):
+ parser = super(ListQuota, self).get_parser(prog_name)
+ return parser
+
+ def get_data(self, parsed_args):
+ self.log.debug('get_data(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ search_opts = {}
+ self.log.debug('search options: %s', search_opts)
+ neutron_client.format = parsed_args.request_format
+ obj_lister = getattr(neutron_client,
+ "list_%ss" % self.resource)
+ data = obj_lister(**search_opts)
+ info = []
+ collection = self.resource + "s"
+ if collection in data:
+ info = data[collection]
+ _columns = len(info) > 0 and sorted(info[0].keys()) or []
+ return (_columns, (utils.get_item_properties(s, _columns)
+ for s in info))
+
+
+class ShowQuota(neutronV20.NeutronCommand, show.ShowOne):
+ """Show quotas of a given tenant
+
+ """
+ api = 'network'
+ resource = "quota"
+ log = logging.getLogger(__name__ + '.ShowQuota')
+
+ def get_parser(self, prog_name):
+ parser = super(ShowQuota, self).get_parser(prog_name)
+ parser.add_argument(
+ '--tenant-id', metavar='tenant-id',
+ help='the owner tenant ID')
+ parser.add_argument(
+ '--tenant_id',
+ help=argparse.SUPPRESS)
+ return parser
+
+ def get_data(self, parsed_args):
+ self.log.debug('get_data(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ tenant_id = get_tenant_id(parsed_args.tenant_id,
+ neutron_client)
+ params = {}
+ obj_shower = getattr(neutron_client,
+ "show_%s" % self.resource)
+ data = obj_shower(tenant_id, **params)
+ if self.resource in data:
+ for k, v in data[self.resource].iteritems():
+ if isinstance(v, list):
+ value = ""
+ for _item in v:
+ if value:
+ value += "\n"
+ if isinstance(_item, dict):
+ value += utils.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].iteritems()))
+ else:
+ return None
+
+
+class UpdateQuota(neutronV20.NeutronCommand, show.ShowOne):
+ """Define tenant's quotas not to use defaults."""
+
+ resource = 'quota'
+ log = logging.getLogger(__name__ + '.UpdateQuota')
+
+ def get_parser(self, prog_name):
+ parser = super(UpdateQuota, self).get_parser(prog_name)
+ parser.add_argument(
+ '--tenant-id', metavar='tenant-id',
+ help='the owner tenant ID')
+ parser.add_argument(
+ '--tenant_id',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--network', metavar='networks',
+ help='the limit of networks')
+ parser.add_argument(
+ '--subnet', metavar='subnets',
+ help='the limit of subnets')
+ parser.add_argument(
+ '--port', metavar='ports',
+ help='the limit of ports')
+ parser.add_argument(
+ '--router', metavar='routers',
+ help='the limit of routers')
+ parser.add_argument(
+ '--floatingip', metavar='floatingips',
+ help='the limit of floating IPs')
+ parser.add_argument(
+ '--security-group', metavar='security_groups',
+ help='the limit of security groups')
+ parser.add_argument(
+ '--security-group-rule', metavar='security_group_rules',
+ help='the limit of security groups rules')
+ return parser
+
+ def _validate_int(self, name, value):
+ try:
+ return_value = int(value)
+ except Exception:
+ message = (_('quota limit for %(name)s must be an integer') %
+ {'name': name})
+ raise exceptions.NeutronClientException(message=message)
+ return return_value
+
+ def args2body(self, parsed_args):
+ quota = {}
+ for resource in ('network', 'subnet', 'port', 'router', 'floatingip',
+ 'security_group', 'security_group_rule'):
+ if getattr(parsed_args, resource):
+ quota[resource] = self._validate_int(
+ resource,
+ getattr(parsed_args, resource))
+ return {self.resource: quota}
+
+ def get_data(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ _extra_values = neutronV20.parse_args_to_dict(self.values_specs)
+ neutronV20._merge_args(self, parsed_args, _extra_values,
+ self.values_specs)
+ body = self.args2body(parsed_args)
+ if self.resource in body:
+ body[self.resource].update(_extra_values)
+ else:
+ body[self.resource] = _extra_values
+ obj_updator = getattr(neutron_client,
+ "update_%s" % self.resource)
+ tenant_id = get_tenant_id(parsed_args.tenant_id,
+ neutron_client)
+ data = obj_updator(tenant_id, body)
+ if self.resource in data:
+ for k, v in data[self.resource].iteritems():
+ if isinstance(v, list):
+ value = ""
+ for _item in v:
+ if value:
+ value += "\n"
+ if isinstance(_item, dict):
+ value += utils.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].iteritems()))
+ else:
+ return None
diff --git a/neutronclient/neutron/v2_0/router.py b/neutronclient/neutron/v2_0/router.py
new file mode 100644
index 0000000..54b8117
--- /dev/null
+++ b/neutronclient/neutron/v2_0/router.py
@@ -0,0 +1,230 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import argparse
+import logging
+
+from neutronclient.common import exceptions
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronV20
+from neutronclient.openstack.common.gettextutils import _
+
+
+def _format_external_gateway_info(router):
+ try:
+ return utils.dumps(router['external_gateway_info'])
+ except Exception:
+ return ''
+
+
+class ListRouter(neutronV20.ListCommand):
+ """List routers that belong to a given tenant."""
+
+ resource = 'router'
+ log = logging.getLogger(__name__ + '.ListRouter')
+ _formatters = {'external_gateway_info': _format_external_gateway_info, }
+ list_columns = ['id', 'name', 'external_gateway_info']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowRouter(neutronV20.ShowCommand):
+ """Show information of a given router."""
+
+ resource = 'router'
+ log = logging.getLogger(__name__ + '.ShowRouter')
+
+
+class CreateRouter(neutronV20.CreateCommand):
+ """Create a router for a given tenant."""
+
+ resource = 'router'
+ log = logging.getLogger(__name__ + '.CreateRouter')
+ _formatters = {'external_gateway_info': _format_external_gateway_info, }
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--admin-state-down',
+ dest='admin_state', action='store_false',
+ help='Set Admin State Up to false')
+ parser.add_argument(
+ '--admin_state_down',
+ dest='admin_state', action='store_false',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ 'name', metavar='NAME',
+ help='Name of router to create')
+
+ def args2body(self, parsed_args):
+ body = {'router': {
+ 'name': parsed_args.name,
+ 'admin_state_up': parsed_args.admin_state, }, }
+ if parsed_args.tenant_id:
+ body['router'].update({'tenant_id': parsed_args.tenant_id})
+ return body
+
+
+class DeleteRouter(neutronV20.DeleteCommand):
+ """Delete a given router."""
+
+ log = logging.getLogger(__name__ + '.DeleteRouter')
+ resource = 'router'
+
+
+class UpdateRouter(neutronV20.UpdateCommand):
+ """Update router's information."""
+
+ log = logging.getLogger(__name__ + '.UpdateRouter')
+ resource = 'router'
+
+
+class RouterInterfaceCommand(neutronV20.NeutronCommand):
+ """Based class to Add/Remove router interface."""
+
+ api = 'network'
+ resource = 'router'
+
+ def call_api(self, neutron_client, router_id, body):
+ raise NotImplementedError()
+
+ def success_message(self, router_id, portinfo):
+ raise NotImplementedError()
+
+ def get_parser(self, prog_name):
+ parser = super(RouterInterfaceCommand, self).get_parser(prog_name)
+ parser.add_argument(
+ 'router_id', metavar='router-id',
+ help='ID of the router')
+ parser.add_argument(
+ 'interface', metavar='INTERFACE',
+ help='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 subnet.')
+ return parser
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+
+ if '=' in parsed_args.interface:
+ resource, value = parsed_args.interface.split('=', 1)
+ if resource not in ['subnet', 'port']:
+ exceptions.CommandError('You must specify either subnet or '
+ 'port for INTERFACE parameter.')
+ else:
+ resource = 'subnet'
+ value = parsed_args.interface
+
+ _router_id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, self.resource, parsed_args.router_id)
+
+ _interface_id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, resource, value)
+ body = {'%s_id' % resource: _interface_id}
+
+ portinfo = self.call_api(neutron_client, _router_id, body)
+ print >>self.app.stdout, self.success_message(parsed_args.router_id,
+ portinfo)
+
+
+class AddInterfaceRouter(RouterInterfaceCommand):
+ """Add an internal network interface to a router."""
+
+ log = logging.getLogger(__name__ + '.AddInterfaceRouter')
+
+ def call_api(self, neutron_client, router_id, body):
+ return neutron_client.add_interface_router(router_id, body)
+
+ def success_message(self, router_id, portinfo):
+ return (_('Added interface %(port)s to router %(router)s.') %
+ {'router': router_id, 'port': portinfo['port_id']})
+
+
+class RemoveInterfaceRouter(RouterInterfaceCommand):
+ """Remove an internal network interface from a router."""
+
+ log = logging.getLogger(__name__ + '.RemoveInterfaceRouter')
+
+ def call_api(self, neutron_client, router_id, body):
+ return neutron_client.remove_interface_router(router_id, body)
+
+ def success_message(self, router_id, portinfo):
+ # portinfo is not used since it is None for router-interface-delete.
+ return _('Removed interface from router %s.') % router_id
+
+
+class SetGatewayRouter(neutronV20.NeutronCommand):
+ """Set the external network gateway for a router."""
+
+ log = logging.getLogger(__name__ + '.SetGatewayRouter')
+ api = 'network'
+ resource = 'router'
+
+ def get_parser(self, prog_name):
+ parser = super(SetGatewayRouter, self).get_parser(prog_name)
+ parser.add_argument(
+ 'router_id', metavar='router-id',
+ help='ID of the router')
+ parser.add_argument(
+ 'external_network_id', metavar='external-network-id',
+ help='ID of the external network for the gateway')
+ parser.add_argument(
+ '--disable-snat', action='store_false', dest='enable_snat',
+ help='Disable Source NAT on the router gateway')
+ return parser
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ _router_id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, self.resource, parsed_args.router_id)
+ _ext_net_id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, 'network', parsed_args.external_network_id)
+ neutron_client.add_gateway_router(
+ _router_id,
+ {'network_id': _ext_net_id,
+ 'enable_snat': parsed_args.enable_snat})
+ print >>self.app.stdout, (
+ _('Set gateway for router %s') % parsed_args.router_id)
+
+
+class RemoveGatewayRouter(neutronV20.NeutronCommand):
+ """Remove an external network gateway from a router."""
+
+ log = logging.getLogger(__name__ + '.RemoveGatewayRouter')
+ api = 'network'
+ resource = 'router'
+
+ def get_parser(self, prog_name):
+ parser = super(RemoveGatewayRouter, self).get_parser(prog_name)
+ parser.add_argument(
+ 'router_id', metavar='router-id',
+ help='ID of the router')
+ return parser
+
+ def run(self, parsed_args):
+ self.log.debug('run(%s)' % parsed_args)
+ neutron_client = self.get_client()
+ neutron_client.format = parsed_args.request_format
+ _router_id = neutronV20.find_resourceid_by_name_or_id(
+ neutron_client, self.resource, parsed_args.router_id)
+ neutron_client.remove_gateway_router(_router_id)
+ print >>self.app.stdout, (
+ _('Removed gateway from router %s') % parsed_args.router_id)
diff --git a/neutronclient/neutron/v2_0/securitygroup.py b/neutronclient/neutron/v2_0/securitygroup.py
new file mode 100644
index 0000000..92570fa
--- /dev/null
+++ b/neutronclient/neutron/v2_0/securitygroup.py
@@ -0,0 +1,259 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import argparse
+import logging
+
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+class ListSecurityGroup(neutronV20.ListCommand):
+ """List security groups that belong to a given tenant."""
+
+ resource = 'security_group'
+ log = logging.getLogger(__name__ + '.ListSecurityGroup')
+ list_columns = ['id', 'name', 'description']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowSecurityGroup(neutronV20.ShowCommand):
+ """Show information of a given security group."""
+
+ resource = 'security_group'
+ log = logging.getLogger(__name__ + '.ShowSecurityGroup')
+ allow_names = True
+
+
+class CreateSecurityGroup(neutronV20.CreateCommand):
+ """Create a security group."""
+
+ resource = 'security_group'
+ log = logging.getLogger(__name__ + '.CreateSecurityGroup')
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'name', metavar='NAME',
+ help='Name of security group')
+ parser.add_argument(
+ '--description',
+ help='description of security group')
+
+ def args2body(self, parsed_args):
+ body = {'security_group': {
+ 'name': parsed_args.name}}
+ if parsed_args.description:
+ body['security_group'].update(
+ {'description': parsed_args.description})
+ if parsed_args.tenant_id:
+ body['security_group'].update({'tenant_id': parsed_args.tenant_id})
+ return body
+
+
+class DeleteSecurityGroup(neutronV20.DeleteCommand):
+ """Delete a given security group."""
+
+ log = logging.getLogger(__name__ + '.DeleteSecurityGroup')
+ resource = 'security_group'
+ allow_names = True
+
+
+class UpdateSecurityGroup(neutronV20.UpdateCommand):
+ """Update a given security group."""
+
+ log = logging.getLogger(__name__ + '.UpdateSecurityGroup')
+ resource = 'security_group'
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--name',
+ help='Name of security group')
+ parser.add_argument(
+ '--description',
+ help='description of security group')
+
+ def args2body(self, parsed_args):
+ body = {'security_group': {}}
+ if parsed_args.name:
+ body['security_group'].update(
+ {'name': parsed_args.name})
+ if parsed_args.description:
+ body['security_group'].update(
+ {'description': parsed_args.description})
+ return body
+
+
+class ListSecurityGroupRule(neutronV20.ListCommand):
+ """List security group rules that belong to a given tenant."""
+
+ resource = 'security_group_rule'
+ log = logging.getLogger(__name__ + '.ListSecurityGroupRule')
+ list_columns = ['id', 'security_group_id', 'direction', 'protocol',
+ 'remote_ip_prefix', 'remote_group_id']
+ replace_rules = {'security_group_id': 'security_group',
+ 'remote_group_id': 'remote_group'}
+ pagination_support = True
+ sorting_support = True
+
+ def get_parser(self, prog_name):
+ parser = super(ListSecurityGroupRule, self).get_parser(prog_name)
+ parser.add_argument(
+ '--no-nameconv', action='store_true',
+ help='Do not convert security group ID to its name')
+ return parser
+
+ @staticmethod
+ def replace_columns(cols, rules, reverse=False):
+ if reverse:
+ rules = dict((rules[k], k) for k in rules.keys())
+ return [rules.get(col, col) for col in cols]
+
+ def retrieve_list(self, parsed_args):
+ parsed_args.fields = self.replace_columns(parsed_args.fields,
+ self.replace_rules,
+ reverse=True)
+ return super(ListSecurityGroupRule, self).retrieve_list(parsed_args)
+
+ def extend_list(self, data, parsed_args):
+ if parsed_args.no_nameconv:
+ return
+ neutron_client = self.get_client()
+ search_opts = {'fields': ['id', 'name']}
+ if self.pagination_support:
+ page_size = parsed_args.page_size
+ if page_size:
+ search_opts.update({'limit': page_size})
+ sec_group_ids = set()
+ for rule in data:
+ for key in self.replace_rules:
+ sec_group_ids.add(rule[key])
+ search_opts.update({"id": sec_group_ids})
+ secgroups = neutron_client.list_security_groups(**search_opts)
+ secgroups = secgroups.get('security_groups', [])
+ sg_dict = dict([(sg['id'], sg['name'])
+ for sg in secgroups if sg['name']])
+ for rule in data:
+ for key in self.replace_rules:
+ rule[key] = sg_dict.get(rule[key], rule[key])
+
+ def setup_columns(self, info, parsed_args):
+ parsed_args.columns = self.replace_columns(parsed_args.columns,
+ self.replace_rules,
+ reverse=True)
+ # NOTE(amotoki): 2nd element of the tuple returned by setup_columns()
+ # is a generator, so if you need to create a look using the generator
+ # object, you need to recreate a generator to show a list expectedly.
+ info = super(ListSecurityGroupRule, self).setup_columns(info,
+ parsed_args)
+ cols = info[0]
+ if not parsed_args.no_nameconv:
+ cols = self.replace_columns(info[0], self.replace_rules)
+ parsed_args.columns = cols
+ return (cols, info[1])
+
+
+class ShowSecurityGroupRule(neutronV20.ShowCommand):
+ """Show information of a given security group rule."""
+
+ resource = 'security_group_rule'
+ log = logging.getLogger(__name__ + '.ShowSecurityGroupRule')
+ allow_names = False
+
+
+class CreateSecurityGroupRule(neutronV20.CreateCommand):
+ """Create a security group rule."""
+
+ resource = 'security_group_rule'
+ log = logging.getLogger(__name__ + '.CreateSecurityGroupRule')
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ 'security_group_id', metavar='SECURITY_GROUP',
+ help='Security group name or id to add rule.')
+ parser.add_argument(
+ '--direction',
+ default='ingress', choices=['ingress', 'egress'],
+ help='direction of traffic: ingress/egress')
+ parser.add_argument(
+ '--ethertype',
+ default='IPv4',
+ help='IPv4/IPv6')
+ parser.add_argument(
+ '--protocol',
+ help='protocol of packet')
+ parser.add_argument(
+ '--port-range-min',
+ help='starting port range')
+ parser.add_argument(
+ '--port_range_min',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--port-range-max',
+ help='ending port range')
+ parser.add_argument(
+ '--port_range_max',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--remote-ip-prefix',
+ help='cidr to match on')
+ parser.add_argument(
+ '--remote_ip_prefix',
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--remote-group-id', metavar='REMOTE_GROUP',
+ help='remote security group name or id to apply rule')
+ parser.add_argument(
+ '--remote_group_id',
+ help=argparse.SUPPRESS)
+
+ def args2body(self, parsed_args):
+ _security_group_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'security_group', parsed_args.security_group_id)
+ body = {'security_group_rule': {
+ 'security_group_id': _security_group_id,
+ 'direction': parsed_args.direction,
+ 'ethertype': parsed_args.ethertype}}
+ if parsed_args.protocol:
+ body['security_group_rule'].update(
+ {'protocol': parsed_args.protocol})
+ if parsed_args.port_range_min:
+ body['security_group_rule'].update(
+ {'port_range_min': parsed_args.port_range_min})
+ if parsed_args.port_range_max:
+ body['security_group_rule'].update(
+ {'port_range_max': parsed_args.port_range_max})
+ if parsed_args.remote_ip_prefix:
+ body['security_group_rule'].update(
+ {'remote_ip_prefix': parsed_args.remote_ip_prefix})
+ if parsed_args.remote_group_id:
+ _remote_group_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'security_group',
+ parsed_args.remote_group_id)
+ body['security_group_rule'].update(
+ {'remote_group_id': _remote_group_id})
+ if parsed_args.tenant_id:
+ body['security_group_rule'].update(
+ {'tenant_id': parsed_args.tenant_id})
+ return body
+
+
+class DeleteSecurityGroupRule(neutronV20.DeleteCommand):
+ """Delete a given security group rule."""
+
+ log = logging.getLogger(__name__ + '.DeleteSecurityGroupRule')
+ resource = 'security_group_rule'
+ allow_names = False
diff --git a/neutronclient/neutron/v2_0/subnet.py b/neutronclient/neutron/v2_0/subnet.py
new file mode 100644
index 0000000..3674db8
--- /dev/null
+++ b/neutronclient/neutron/v2_0/subnet.py
@@ -0,0 +1,168 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import argparse
+import logging
+
+from neutronclient.common import exceptions
+from neutronclient.common import utils
+from neutronclient.neutron import v2_0 as neutronV20
+
+
+def _format_allocation_pools(subnet):
+ try:
+ return '\n'.join([utils.dumps(pool) for pool in
+ subnet['allocation_pools']])
+ except Exception:
+ return ''
+
+
+def _format_dns_nameservers(subnet):
+ try:
+ return '\n'.join([utils.dumps(server) for server in
+ subnet['dns_nameservers']])
+ except Exception:
+ return ''
+
+
+def _format_host_routes(subnet):
+ try:
+ return '\n'.join([utils.dumps(route) for route in
+ subnet['host_routes']])
+ except Exception:
+ return ''
+
+
+class ListSubnet(neutronV20.ListCommand):
+ """List networks that belong to a given tenant."""
+
+ resource = 'subnet'
+ log = logging.getLogger(__name__ + '.ListSubnet')
+ _formatters = {'allocation_pools': _format_allocation_pools,
+ 'dns_nameservers': _format_dns_nameservers,
+ 'host_routes': _format_host_routes, }
+ list_columns = ['id', 'name', 'cidr', 'allocation_pools']
+ pagination_support = True
+ sorting_support = True
+
+
+class ShowSubnet(neutronV20.ShowCommand):
+ """Show information of a given subnet."""
+
+ resource = 'subnet'
+ log = logging.getLogger(__name__ + '.ShowSubnet')
+
+
+class CreateSubnet(neutronV20.CreateCommand):
+ """Create a subnet for a given tenant."""
+
+ resource = 'subnet'
+ log = logging.getLogger(__name__ + '.CreateSubnet')
+
+ def add_known_arguments(self, parser):
+ parser.add_argument(
+ '--name',
+ help='name of this subnet')
+ parser.add_argument(
+ '--ip-version',
+ type=int,
+ default=4, choices=[4, 6],
+ help='IP version with default 4')
+ parser.add_argument(
+ '--ip_version',
+ type=int,
+ choices=[4, 6],
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--gateway', metavar='GATEWAY_IP',
+ help='gateway ip of this subnet')
+ parser.add_argument(
+ '--no-gateway',
+ action='store_true',
+ 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,
+ 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,
+ help=argparse.SUPPRESS)
+ parser.add_argument(
+ '--host-route', metavar='destination=CIDR,nexthop=IP_ADDR',
+ action='append', dest='host_routes', type=utils.str2dict,
+ help='Additional route (This option can be repeated)')
+ parser.add_argument(
+ '--dns-nameserver', metavar='DNS_NAMESERVER',
+ action='append', dest='dns_nameservers',
+ help='DNS name server for this subnet '
+ '(This option can be repeated)')
+ parser.add_argument(
+ '--disable-dhcp',
+ action='store_true',
+ help='Disable DHCP for this subnet')
+ parser.add_argument(
+ 'network_id', metavar='NETWORK',
+ help='network id or name this subnet belongs to')
+ parser.add_argument(
+ 'cidr', metavar='CIDR',
+ help='cidr of subnet to create')
+
+ def args2body(self, parsed_args):
+ _network_id = neutronV20.find_resourceid_by_name_or_id(
+ self.get_client(), 'network', parsed_args.network_id)
+ body = {'subnet': {'cidr': parsed_args.cidr,
+ 'network_id': _network_id,
+ 'ip_version': parsed_args.ip_version, }, }
+
+ if parsed_args.gateway and parsed_args.no_gateway:
+ raise exceptions.CommandError("--gateway option and "
+ "--no-gateway option can "
+ "not be used same time")
+ if parsed_args.no_gateway:
+ body['subnet'].update({'gateway_ip': None})
+ if parsed_args.gateway:
+ body['subnet'].update({'gateway_ip': parsed_args.gateway})
+ if parsed_args.tenant_id:
+ body['subnet'].update({'tenant_id': parsed_args.tenant_id})
+ if parsed_args.name:
+ body['subnet'].update({'name': parsed_args.name})
+ if parsed_args.disable_dhcp:
+ body['subnet'].update({'enable_dhcp': False})
+ if parsed_args.allocation_pools:
+ body['subnet']['allocation_pools'] = parsed_args.allocation_pools
+ if parsed_args.host_routes:
+ body['subnet']['host_routes'] = parsed_args.host_routes
+ if parsed_args.dns_nameservers:
+ body['subnet']['dns_nameservers'] = parsed_args.dns_nameservers
+
+ return body
+
+
+class DeleteSubnet(neutronV20.DeleteCommand):
+ """Delete a given subnet."""
+
+ resource = 'subnet'
+ log = logging.getLogger(__name__ + '.DeleteSubnet')
+
+
+class UpdateSubnet(neutronV20.UpdateCommand):
+ """Update subnet's information."""
+
+ resource = 'subnet'
+ log = logging.getLogger(__name__ + '.UpdateSubnet')
diff --git a/neutronclient/openstack/__init__.py b/neutronclient/openstack/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutronclient/openstack/__init__.py
diff --git a/neutronclient/openstack/common/__init__.py b/neutronclient/openstack/common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutronclient/openstack/common/__init__.py
diff --git a/neutronclient/openstack/common/exception.py b/neutronclient/openstack/common/exception.py
new file mode 100644
index 0000000..bb6e6dc
--- /dev/null
+++ b/neutronclient/openstack/common/exception.py
@@ -0,0 +1,142 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 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.
+
+"""
+Exceptions common to OpenStack projects
+"""
+
+import logging
+
+from neutronclient.openstack.common.gettextutils import _
+
+_FATAL_EXCEPTION_FORMAT_ERRORS = False
+
+
+class Error(Exception):
+ def __init__(self, message=None):
+ super(Error, self).__init__(message)
+
+
+class ApiError(Error):
+ def __init__(self, message='Unknown', code='Unknown'):
+ self.message = message
+ self.code = code
+ super(ApiError, self).__init__('%s: %s' % (code, message))
+
+
+class NotFound(Error):
+ pass
+
+
+class UnknownScheme(Error):
+
+ msg = "Unknown scheme '%s' found in URI"
+
+ def __init__(self, scheme):
+ msg = self.__class__.msg % scheme
+ super(UnknownScheme, self).__init__(msg)
+
+
+class BadStoreUri(Error):
+
+ msg = "The Store URI %s was malformed. Reason: %s"
+
+ def __init__(self, uri, reason):
+ msg = self.__class__.msg % (uri, reason)
+ super(BadStoreUri, self).__init__(msg)
+
+
+class Duplicate(Error):
+ pass
+
+
+class NotAuthorized(Error):
+ pass
+
+
+class NotEmpty(Error):
+ pass
+
+
+class Invalid(Error):
+ pass
+
+
+class BadInputError(Exception):
+ """Error resulting from a client sending bad input to a server"""
+ pass
+
+
+class MissingArgumentError(Error):
+ pass
+
+
+class DatabaseMigrationError(Error):
+ pass
+
+
+class ClientConnectionError(Exception):
+ """Error resulting from a client connecting to a server"""
+ pass
+
+
+def wrap_exception(f):
+ def _wrap(*args, **kw):
+ try:
+ return f(*args, **kw)
+ except Exception, e:
+ if not isinstance(e, Error):
+ #exc_type, exc_value, exc_traceback = sys.exc_info()
+ logging.exception(_('Uncaught exception'))
+ #logging.error(traceback.extract_stack(exc_traceback))
+ raise Error(str(e))
+ raise
+ _wrap.func_name = f.func_name
+ return _wrap
+
+
+class OpenstackException(Exception):
+ """
+ Base Exception
+
+ To correctly use this class, inherit from it and define
+ a 'message' property. That message will get printf'd
+ with the keyword arguments provided to the constructor.
+ """
+ message = "An unknown exception occurred"
+
+ def __init__(self, **kwargs):
+ try:
+ self._error_string = self.message % kwargs
+
+ except Exception as e:
+ if _FATAL_EXCEPTION_FORMAT_ERRORS:
+ raise e
+ else:
+ # at least get the core message out if something happened
+ self._error_string = self.message
+
+ def __str__(self):
+ return self._error_string
+
+
+class MalformedRequestBody(OpenstackException):
+ message = "Malformed message body: %(reason)s"
+
+
+class InvalidContentType(OpenstackException):
+ message = "Invalid content type %(content_type)s"
diff --git a/neutronclient/openstack/common/gettextutils.py b/neutronclient/openstack/common/gettextutils.py
new file mode 100644
index 0000000..5c597c7
--- /dev/null
+++ b/neutronclient/openstack/common/gettextutils.py
@@ -0,0 +1,33 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Red Hat, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+gettext for openstack-common modules.
+
+Usual usage in an openstack.common module:
+
+ from neutronclient.openstack.common.gettextutils import _
+"""
+
+import gettext
+
+
+t = gettext.translation('openstack-common', 'locale', fallback=True)
+
+
+def _(msg):
+ return t.ugettext(msg)
diff --git a/neutronclient/openstack/common/jsonutils.py b/neutronclient/openstack/common/jsonutils.py
new file mode 100644
index 0000000..9ff7415
--- /dev/null
+++ b/neutronclient/openstack/common/jsonutils.py
@@ -0,0 +1,148 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Justin Santa Barbara
+# 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.
+
+'''
+JSON related utilities.
+
+This module provides a few things:
+
+ 1) A handy function for getting an object down to something that can be
+ JSON serialized. See to_primitive().
+
+ 2) Wrappers around loads() and dumps(). The dumps() wrapper will
+ automatically use to_primitive() for you if needed.
+
+ 3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson
+ is available.
+'''
+
+
+import datetime
+import inspect
+import itertools
+import json
+import xmlrpclib
+
+from neutronclient.openstack.common import timeutils
+
+
+def to_primitive(value, convert_instances=False, level=0):
+ """Convert a complex object into primitives.
+
+ Handy for JSON serialization. We can optionally handle instances,
+ but since this is a recursive function, we could have cyclical
+ data structures.
+
+ To handle cyclical data structures we could track the actual objects
+ visited in a set, but not all objects are hashable. Instead we just
+ track the depth of the object inspections and don't go too deep.
+
+ Therefore, convert_instances=True is lossy ... be aware.
+
+ """
+ nasty = [inspect.ismodule, inspect.isclass, inspect.ismethod,
+ inspect.isfunction, inspect.isgeneratorfunction,
+ inspect.isgenerator, inspect.istraceback, inspect.isframe,
+ inspect.iscode, inspect.isbuiltin, inspect.isroutine,
+ inspect.isabstract]
+ for test in nasty:
+ if test(value):
+ return unicode(value)
+
+ # value of itertools.count doesn't get caught by inspects
+ # above and results in infinite loop when list(value) is called.
+ if type(value) == itertools.count:
+ return unicode(value)
+
+ # FIXME(vish): Workaround for LP bug 852095. Without this workaround,
+ # tests that raise an exception in a mocked method that
+ # has a @wrap_exception with a notifier will fail. If
+ # we up the dependency to 0.5.4 (when it is released) we
+ # can remove this workaround.
+ if getattr(value, '__module__', None) == 'mox':
+ return 'mock'
+
+ if level > 3:
+ return '?'
+
+ # The try block may not be necessary after the class check above,
+ # but just in case ...
+ try:
+ # It's not clear why xmlrpclib created their own DateTime type, but
+ # for our purposes, make it a datetime type which is explicitly
+ # handled
+ if isinstance(value, xmlrpclib.DateTime):
+ value = datetime.datetime(*tuple(value.timetuple())[:6])
+
+ if isinstance(value, (list, tuple)):
+ o = []
+ for v in value:
+ o.append(to_primitive(v, convert_instances=convert_instances,
+ level=level))
+ return o
+ elif isinstance(value, dict):
+ o = {}
+ for k, v in value.iteritems():
+ o[k] = to_primitive(v, convert_instances=convert_instances,
+ level=level)
+ return o
+ elif isinstance(value, datetime.datetime):
+ return timeutils.strtime(value)
+ elif hasattr(value, 'iteritems'):
+ return to_primitive(dict(value.iteritems()),
+ convert_instances=convert_instances,
+ level=level + 1)
+ elif hasattr(value, '__iter__'):
+ return to_primitive(list(value),
+ convert_instances=convert_instances,
+ level=level)
+ elif convert_instances and hasattr(value, '__dict__'):
+ # Likely an instance of something. Watch for cycles.
+ # Ignore class member vars.
+ return to_primitive(value.__dict__,
+ convert_instances=convert_instances,
+ level=level + 1)
+ else:
+ return value
+ except TypeError:
+ # Class objects are tricky since they may define something like
+ # __iter__ defined but it isn't callable as list().
+ return unicode(value)
+
+
+def dumps(value, default=to_primitive, **kwargs):
+ return json.dumps(value, default=default, **kwargs)
+
+
+def loads(s):
+ return json.loads(s)
+
+
+def load(s):
+ return json.load(s)
+
+
+try:
+ import anyjson
+except ImportError:
+ pass
+else:
+ anyjson._modules.append((__name__, 'dumps', TypeError,
+ 'loads', ValueError, 'load'))
+ anyjson.force_implementation(__name__)
diff --git a/neutronclient/openstack/common/strutils.py b/neutronclient/openstack/common/strutils.py
new file mode 100644
index 0000000..ecf3cfd
--- /dev/null
+++ b/neutronclient/openstack/common/strutils.py
@@ -0,0 +1,133 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 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.
+
+"""
+System-level utilities and helper functions.
+"""
+
+import logging
+import sys
+
+LOG = logging.getLogger(__name__)
+
+
+def int_from_bool_as_string(subject):
+ """
+ Interpret a string as a boolean and return either 1 or 0.
+
+ Any string value in:
+
+ ('True', 'true', 'On', 'on', '1')
+
+ is interpreted as a boolean True.
+
+ Useful for JSON-decoded stuff and config file parsing
+ """
+ return bool_from_string(subject) and 1 or 0
+
+
+def bool_from_string(subject):
+ """
+ Interpret a string as a boolean.
+
+ Any string value in:
+
+ ('True', 'true', 'On', 'on', 'Yes', 'yes', '1')
+
+ is interpreted as a boolean True.
+
+ Useful for JSON-decoded stuff and config file parsing
+ """
+ if isinstance(subject, bool):
+ return subject
+ if isinstance(subject, basestring):
+ if subject.strip().lower() in ('true', 'on', 'yes', '1'):
+ return True
+ return False
+
+
+def safe_decode(text, incoming=None, errors='strict'):
+ """
+ Decodes incoming str using `incoming` if they're
+ not already unicode.
+
+ :param incoming: Text's current encoding
+ :param errors: Errors handling policy. See here for valid
+ values http://docs.python.org/2/library/codecs.html
+ :returns: text or a unicode `incoming` encoded
+ representation of it.
+ :raises TypeError: If text is not an isntance of basestring
+ """
+ if not isinstance(text, basestring):
+ raise TypeError("%s can't be decoded" % type(text))
+
+ if isinstance(text, unicode):
+ return text
+
+ if not incoming:
+ incoming = (sys.stdin.encoding or
+ sys.getdefaultencoding())
+
+ try:
+ return text.decode(incoming, errors)
+ except UnicodeDecodeError:
+ # Note(flaper87) If we get here, it means that
+ # sys.stdin.encoding / sys.getdefaultencoding
+ # didn't return a suitable encoding to decode
+ # text. This happens mostly when global LANG
+ # var is not set correctly and there's no
+ # default encoding. In this case, most likely
+ # python will use ASCII or ANSI encoders as
+ # default encodings but they won't be capable
+ # of decoding non-ASCII characters.
+ #
+ # Also, UTF-8 is being used since it's an ASCII
+ # extension.
+ return text.decode('utf-8', errors)
+
+
+def safe_encode(text, incoming=None,
+ encoding='utf-8', errors='strict'):
+ """
+ Encodes incoming str/unicode using `encoding`. If
+ incoming is not specified, text is expected to
+ be encoded with current python's default encoding.
+ (`sys.getdefaultencoding`)
+
+ :param incoming: Text's current encoding
+ :param encoding: Expected encoding for text (Default UTF-8)
+ :param errors: Errors handling policy. See here for valid
+ values http://docs.python.org/2/library/codecs.html
+ :returns: text or a bytestring `encoding` encoded
+ representation of it.
+ :raises TypeError: If text is not an isntance of basestring
+ """
+ if not isinstance(text, basestring):
+ raise TypeError("%s can't be encoded" % type(text))
+
+ if not incoming:
+ incoming = (sys.stdin.encoding or
+ sys.getdefaultencoding())
+
+ if isinstance(text, unicode):
+ return text.encode(encoding, errors)
+ elif text and encoding != incoming:
+ # Decode text before encoding it with `encoding`
+ text = safe_decode(text, incoming, errors)
+ return text.encode(encoding, errors)
+
+ return text
diff --git a/neutronclient/openstack/common/timeutils.py b/neutronclient/openstack/common/timeutils.py
new file mode 100644
index 0000000..0f34608
--- /dev/null
+++ b/neutronclient/openstack/common/timeutils.py
@@ -0,0 +1,164 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# 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.
+
+"""
+Time related utilities and helper functions.
+"""
+
+import calendar
+import datetime
+
+import iso8601
+
+
+TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
+PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f"
+
+
+def isotime(at=None):
+ """Stringify time in ISO 8601 format"""
+ if not at:
+ at = utcnow()
+ str = at.strftime(TIME_FORMAT)
+ tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC'
+ str += ('Z' if tz == 'UTC' else tz)
+ return str
+
+
+def parse_isotime(timestr):
+ """Parse time from ISO 8601 format"""
+ try:
+ return iso8601.parse_date(timestr)
+ except iso8601.ParseError as e:
+ raise ValueError(e.message)
+ except TypeError as e:
+ raise ValueError(e.message)
+
+
+def strtime(at=None, fmt=PERFECT_TIME_FORMAT):
+ """Returns formatted utcnow."""
+ if not at:
+ at = utcnow()
+ return at.strftime(fmt)
+
+
+def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT):
+ """Turn a formatted time back into a datetime."""
+ return datetime.datetime.strptime(timestr, fmt)
+
+
+def normalize_time(timestamp):
+ """Normalize time in arbitrary timezone to UTC naive object"""
+ offset = timestamp.utcoffset()
+ if offset is None:
+ return timestamp
+ return timestamp.replace(tzinfo=None) - offset
+
+
+def is_older_than(before, seconds):
+ """Return True if before is older than seconds."""
+ if isinstance(before, basestring):
+ before = parse_strtime(before).replace(tzinfo=None)
+ return utcnow() - before > datetime.timedelta(seconds=seconds)
+
+
+def is_newer_than(after, seconds):
+ """Return True if after is newer than seconds."""
+ if isinstance(after, basestring):
+ after = parse_strtime(after).replace(tzinfo=None)
+ return after - utcnow() > datetime.timedelta(seconds=seconds)
+
+
+def utcnow_ts():
+ """Timestamp version of our utcnow function."""
+ return calendar.timegm(utcnow().timetuple())
+
+
+def utcnow():
+ """Overridable version of utils.utcnow."""
+ if utcnow.override_time:
+ try:
+ return utcnow.override_time.pop(0)
+ except AttributeError:
+ return utcnow.override_time
+ return datetime.datetime.utcnow()
+
+
+utcnow.override_time = None
+
+
+def set_time_override(override_time=datetime.datetime.utcnow()):
+ """
+ Override utils.utcnow to return a constant time or a list thereof,
+ one at a time.
+ """
+ utcnow.override_time = override_time
+
+
+def advance_time_delta(timedelta):
+ """Advance overridden time using a datetime.timedelta."""
+ assert(not utcnow.override_time is None)
+ try:
+ for dt in utcnow.override_time:
+ dt += timedelta
+ except TypeError:
+ utcnow.override_time += timedelta
+
+
+def advance_time_seconds(seconds):
+ """Advance overridden time by seconds."""
+ advance_time_delta(datetime.timedelta(0, seconds))
+
+
+def clear_time_override():
+ """Remove the overridden time."""
+ utcnow.override_time = None
+
+
+def marshall_now(now=None):
+ """Make an rpc-safe datetime with microseconds.
+
+ Note: tzinfo is stripped, but not required for relative times."""
+ if not now:
+ now = utcnow()
+ return dict(day=now.day, month=now.month, year=now.year, hour=now.hour,
+ minute=now.minute, second=now.second,
+ microsecond=now.microsecond)
+
+
+def unmarshall_time(tyme):
+ """Unmarshall a datetime dict."""
+ return datetime.datetime(day=tyme['day'],
+ month=tyme['month'],
+ year=tyme['year'],
+ hour=tyme['hour'],
+ minute=tyme['minute'],
+ second=tyme['second'],
+ microsecond=tyme['microsecond'])
+
+
+def delta_seconds(before, after):
+ """
+ Compute the difference in seconds between two date, time, or
+ datetime objects (as a float, to microsecond resolution).
+ """
+ delta = after - before
+ try:
+ return delta.total_seconds()
+ except AttributeError:
+ return ((delta.days * 24 * 3600) + delta.seconds +
+ float(delta.microseconds) / (10 ** 6))
diff --git a/neutronclient/shell.py b/neutronclient/shell.py
new file mode 100644
index 0000000..8cc6441
--- /dev/null
+++ b/neutronclient/shell.py
@@ -0,0 +1,575 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+"""
+Command-line interface to the Neutron APIs
+"""
+
+import argparse
+import logging
+import os
+import sys
+
+from cliff import app
+from cliff import commandmanager
+
+from neutronclient.common import clientmanager
+from neutronclient.common import exceptions as exc
+from neutronclient.common import utils
+from neutronclient.neutron.v2_0 import agent
+from neutronclient.neutron.v2_0 import agentscheduler
+from neutronclient.neutron.v2_0 import extension
+from neutronclient.neutron.v2_0 import floatingip
+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 import vip as lb_vip
+from neutronclient.neutron.v2_0 import network
+from neutronclient.neutron.v2_0 import nvp_qos_queue
+from neutronclient.neutron.v2_0 import nvpnetworkgateway
+from neutronclient.neutron.v2_0 import port
+from neutronclient.neutron.v2_0 import quota
+from neutronclient.neutron.v2_0 import router
+from neutronclient.neutron.v2_0 import securitygroup
+from neutronclient.neutron.v2_0 import subnet
+from neutronclient.openstack.common import strutils
+from neutronclient.version import __version__
+
+
+VERSION = '2.0'
+NEUTRON_API_VERSION = '2.0'
+
+
+def run_command(cmd, cmd_parser, sub_argv):
+ _argv = sub_argv
+ index = -1
+ values_specs = []
+ if '--' in sub_argv:
+ index = sub_argv.index('--')
+ _argv = sub_argv[:index]
+ values_specs = sub_argv[index:]
+ known_args, _values_specs = cmd_parser.parse_known_args(_argv)
+ cmd.values_specs = (index == -1 and _values_specs or values_specs)
+ return cmd.run(known_args)
+
+
+def env(*_vars, **kwargs):
+ """Search for the first defined of possibly many env vars
+
+ Returns the first environment variable defined in vars, or
+ returns the default defined in kwargs.
+
+ """
+ for v in _vars:
+ value = os.environ.get(v, None)
+ if value:
+ return value
+ return kwargs.get('default', '')
+
+
+COMMAND_V2 = {
+ 'net-list': network.ListNetwork,
+ 'net-external-list': network.ListExternalNetwork,
+ 'net-show': network.ShowNetwork,
+ 'net-create': network.CreateNetwork,
+ 'net-delete': network.DeleteNetwork,
+ 'net-update': network.UpdateNetwork,
+ 'subnet-list': subnet.ListSubnet,
+ 'subnet-show': subnet.ShowSubnet,
+ 'subnet-create': subnet.CreateSubnet,
+ 'subnet-delete': subnet.DeleteSubnet,
+ 'subnet-update': subnet.UpdateSubnet,
+ 'port-list': port.ListPort,
+ 'port-show': port.ShowPort,
+ 'port-create': port.CreatePort,
+ 'port-delete': port.DeletePort,
+ 'port-update': port.UpdatePort,
+ 'quota-list': quota.ListQuota,
+ 'quota-show': quota.ShowQuota,
+ 'quota-delete': quota.DeleteQuota,
+ 'quota-update': quota.UpdateQuota,
+ 'ext-list': extension.ListExt,
+ 'ext-show': extension.ShowExt,
+ 'router-list': router.ListRouter,
+ 'router-port-list': port.ListRouterPort,
+ 'router-show': router.ShowRouter,
+ 'router-create': router.CreateRouter,
+ 'router-delete': router.DeleteRouter,
+ 'router-update': router.UpdateRouter,
+ 'router-interface-add': router.AddInterfaceRouter,
+ 'router-interface-delete': router.RemoveInterfaceRouter,
+ 'router-gateway-set': router.SetGatewayRouter,
+ 'router-gateway-clear': router.RemoveGatewayRouter,
+ 'floatingip-list': floatingip.ListFloatingIP,
+ 'floatingip-show': floatingip.ShowFloatingIP,
+ 'floatingip-create': floatingip.CreateFloatingIP,
+ 'floatingip-delete': floatingip.DeleteFloatingIP,
+ 'floatingip-associate': floatingip.AssociateFloatingIP,
+ 'floatingip-disassociate': floatingip.DisassociateFloatingIP,
+ 'security-group-list': securitygroup.ListSecurityGroup,
+ 'security-group-show': securitygroup.ShowSecurityGroup,
+ 'security-group-create': securitygroup.CreateSecurityGroup,
+ 'security-group-delete': securitygroup.DeleteSecurityGroup,
+ 'security-group-update': securitygroup.UpdateSecurityGroup,
+ 'security-group-rule-list': securitygroup.ListSecurityGroupRule,
+ 'security-group-rule-show': securitygroup.ShowSecurityGroupRule,
+ 'security-group-rule-create': securitygroup.CreateSecurityGroupRule,
+ 'security-group-rule-delete': securitygroup.DeleteSecurityGroupRule,
+ 'lb-vip-list': lb_vip.ListVip,
+ 'lb-vip-show': lb_vip.ShowVip,
+ 'lb-vip-create': lb_vip.CreateVip,
+ 'lb-vip-update': lb_vip.UpdateVip,
+ 'lb-vip-delete': lb_vip.DeleteVip,
+ 'lb-pool-list': lb_pool.ListPool,
+ 'lb-pool-show': lb_pool.ShowPool,
+ 'lb-pool-create': lb_pool.CreatePool,
+ 'lb-pool-update': lb_pool.UpdatePool,
+ 'lb-pool-delete': lb_pool.DeletePool,
+ 'lb-pool-stats': lb_pool.RetrievePoolStats,
+ 'lb-member-list': lb_member.ListMember,
+ 'lb-member-show': lb_member.ShowMember,
+ 'lb-member-create': lb_member.CreateMember,
+ 'lb-member-update': lb_member.UpdateMember,
+ 'lb-member-delete': lb_member.DeleteMember,
+ 'lb-healthmonitor-list': lb_healthmonitor.ListHealthMonitor,
+ 'lb-healthmonitor-show': lb_healthmonitor.ShowHealthMonitor,
+ 'lb-healthmonitor-create': lb_healthmonitor.CreateHealthMonitor,
+ 'lb-healthmonitor-update': lb_healthmonitor.UpdateHealthMonitor,
+ 'lb-healthmonitor-delete': lb_healthmonitor.DeleteHealthMonitor,
+ 'lb-healthmonitor-associate': lb_healthmonitor.AssociateHealthMonitor,
+ 'lb-healthmonitor-disassociate': (
+ lb_healthmonitor.DisassociateHealthMonitor
+ ),
+ 'queue-create': nvp_qos_queue.CreateQoSQueue,
+ 'queue-delete': nvp_qos_queue.DeleteQoSQueue,
+ 'queue-show': nvp_qos_queue.ShowQoSQueue,
+ 'queue-list': nvp_qos_queue.ListQoSQueue,
+ 'agent-list': agent.ListAgent,
+ 'agent-show': agent.ShowAgent,
+ 'agent-delete': agent.DeleteAgent,
+ 'agent-update': agent.UpdateAgent,
+ 'net-gateway-create': nvpnetworkgateway.CreateNetworkGateway,
+ 'net-gateway-update': nvpnetworkgateway.UpdateNetworkGateway,
+ 'net-gateway-delete': nvpnetworkgateway.DeleteNetworkGateway,
+ 'net-gateway-show': nvpnetworkgateway.ShowNetworkGateway,
+ 'net-gateway-list': nvpnetworkgateway.ListNetworkGateway,
+ 'net-gateway-connect': nvpnetworkgateway.ConnectNetworkGateway,
+ 'net-gateway-disconnect': nvpnetworkgateway.DisconnectNetworkGateway,
+ 'dhcp-agent-network-add': agentscheduler.AddNetworkToDhcpAgent,
+ 'dhcp-agent-network-remove': agentscheduler.RemoveNetworkFromDhcpAgent,
+ 'net-list-on-dhcp-agent': agentscheduler.ListNetworksOnDhcpAgent,
+ 'dhcp-agent-list-hosting-net': agentscheduler.ListDhcpAgentsHostingNetwork,
+ 'l3-agent-router-add': agentscheduler.AddRouterToL3Agent,
+ 'l3-agent-router-remove': agentscheduler.RemoveRouterFromL3Agent,
+ 'router-list-on-l3-agent': agentscheduler.ListRoutersOnL3Agent,
+ 'l3-agent-list-hosting-router': agentscheduler.ListL3AgentsHostingRouter,
+}
+
+COMMANDS = {'2.0': COMMAND_V2}
+
+
+class HelpAction(argparse.Action):
+ """Provide a custom action so the -h and --help options
+ to the main app will print a list of the commands.
+
+ The commands are determined by checking the CommandManager
+ instance, passed in as the "default" value for the action.
+ """
+ def __call__(self, parser, namespace, values, option_string=None):
+ outputs = []
+ max_len = 0
+ app = self.default
+ parser.print_help(app.stdout)
+ app.stdout.write('\nCommands for API v%s:\n' % app.api_version)
+ command_manager = app.command_manager
+ for name, ep in sorted(command_manager):
+ factory = ep.load()
+ cmd = factory(self, None)
+ one_liner = cmd.get_description().split('\n')[0]
+ outputs.append((name, one_liner))
+ max_len = max(len(name), max_len)
+ for (name, one_liner) in outputs:
+ app.stdout.write(' %s %s\n' % (name.ljust(max_len), one_liner))
+ sys.exit(0)
+
+
+class NeutronShell(app.App):
+
+ CONSOLE_MESSAGE_FORMAT = '%(message)s'
+ DEBUG_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s'
+ log = logging.getLogger(__name__)
+
+ def __init__(self, apiversion):
+ super(NeutronShell, self).__init__(
+ description=__doc__.strip(),
+ version=VERSION,
+ command_manager=commandmanager.CommandManager('neutron.cli'), )
+ self.commands = COMMANDS
+ for k, v in self.commands[apiversion].items():
+ self.command_manager.add_command(k, v)
+
+ # This is instantiated in initialize_app() only when using
+ # password flow auth
+ self.auth_client = None
+ self.api_version = apiversion
+
+ def build_option_parser(self, description, version):
+ """Return an argparse option parser for this application.
+
+ Subclasses may override this method to extend
+ the parser with more global options.
+
+ :param description: full description of the application
+ :paramtype description: str
+ :param version: version number for the application
+ :paramtype version: str
+ """
+ parser = argparse.ArgumentParser(
+ description=description,
+ add_help=False, )
+ parser.add_argument(
+ '--version',
+ action='version',
+ version=__version__, )
+ parser.add_argument(
+ '-v', '--verbose',
+ action='count',
+ dest='verbose_level',
+ default=self.DEFAULT_VERBOSE_LEVEL,
+ help='Increase verbosity of output. Can be repeated.', )
+ parser.add_argument(
+ '-q', '--quiet',
+ action='store_const',
+ dest='verbose_level',
+ const=0,
+ help='suppress output except warnings and errors', )
+ parser.add_argument(
+ '-h', '--help',
+ action=HelpAction,
+ nargs=0,
+ default=self, # tricky
+ help="show this help message and exit", )
+ parser.add_argument(
+ '--debug',
+ default=False,
+ action='store_true',
+ help='show tracebacks on errors', )
+ # Global arguments
+ parser.add_argument(
+ '--os-auth-strategy', metavar='<auth-strategy>',
+ default=env('OS_AUTH_STRATEGY', default='keystone'),
+ help='Authentication strategy (Env: OS_AUTH_STRATEGY'
+ ', default keystone). For now, any other value will'
+ ' disable the authentication')
+ parser.add_argument(
+ '--os_auth_strategy',
+ help=argparse.SUPPRESS)
+
+ parser.add_argument(
+ '--os-auth-url', metavar='<auth-url>',
+ default=env('OS_AUTH_URL'),
+ help='Authentication URL (Env: OS_AUTH_URL)')
+ parser.add_argument(
+ '--os_auth_url',
+ help=argparse.SUPPRESS)
+
+ parser.add_argument(
+ '--os-tenant-name', metavar='<auth-tenant-name>',
+ default=env('OS_TENANT_NAME'),
+ help='Authentication tenant name (Env: OS_TENANT_NAME)')
+ parser.add_argument(
+ '--os_tenant_name',
+ help=argparse.SUPPRESS)
+
+ parser.add_argument(
+ '--os-username', metavar='<auth-username>',
+ default=utils.env('OS_USERNAME'),
+ help='Authentication username (Env: OS_USERNAME)')
+ parser.add_argument(
+ '--os_username',
+ help=argparse.SUPPRESS)
+
+ parser.add_argument(
+ '--os-password', metavar='<auth-password>',
+ default=utils.env('OS_PASSWORD'),
+ help='Authentication password (Env: OS_PASSWORD)')
+ parser.add_argument(
+ '--os_password',
+ help=argparse.SUPPRESS)
+
+ parser.add_argument(
+ '--os-region-name', metavar='<auth-region-name>',
+ default=env('OS_REGION_NAME'),
+ help='Authentication region name (Env: OS_REGION_NAME)')
+ parser.add_argument(
+ '--os_region_name',
+ help=argparse.SUPPRESS)
+
+ parser.add_argument(
+ '--os-token', metavar='<token>',
+ default=env('OS_TOKEN'),
+ help='Defaults to env[OS_TOKEN]')
+ parser.add_argument(
+ '--os_token',
+ help=argparse.SUPPRESS)
+
+ parser.add_argument(
+ '--endpoint-type', metavar='<endpoint-type>',
+ default=env('OS_ENDPOINT_TYPE', default='publicURL'),
+ help='Defaults to env[OS_ENDPOINT_TYPE] or publicURL.')
+
+ parser.add_argument(
+ '--os-url', metavar='<url>',
+ default=env('OS_URL'),
+ help='Defaults to env[OS_URL]')
+ parser.add_argument(
+ '--os_url',
+ help=argparse.SUPPRESS)
+
+ parser.add_argument(
+ '--insecure',
+ action='store_true',
+ default=env('NEUTRONCLIENT_INSECURE', default=False),
+ help="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.")
+
+ return parser
+
+ def _bash_completion(self):
+ """Prints all of the commands and options for bash-completion."""
+ commands = set()
+ 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()
+ cmd = cmd_factory(self, None)
+ cmd_parser = cmd.get_parser('')
+ for option, _action in cmd_parser._option_string_actions.items():
+ options.add(option)
+ print ' '.join(commands | options)
+
+ def run(self, argv):
+ """Equivalent to the main program for the application.
+
+ :param argv: input arguments and options
+ :paramtype argv: list of str
+ """
+ try:
+ index = 0
+ command_pos = -1
+ help_pos = -1
+ help_command_pos = -1
+ for arg in argv:
+ if arg == 'bash-completion':
+ self._bash_completion()
+ return 0
+ if arg in self.commands[self.api_version]:
+ if command_pos == -1:
+ command_pos = index
+ elif arg in ('-h', '--help'):
+ if help_pos == -1:
+ help_pos = index
+ elif arg == 'help':
+ if help_command_pos == -1:
+ help_command_pos = index
+ index = index + 1
+ if command_pos > -1 and help_pos > command_pos:
+ argv = ['help', argv[command_pos]]
+ if help_command_pos > -1 and command_pos == -1:
+ argv[help_command_pos] = '--help'
+ self.options, remainder = self.parser.parse_known_args(argv)
+ self.configure_logging()
+ self.interactive_mode = not remainder
+ self.initialize_app(remainder)
+ except Exception as err:
+ if self.options.debug:
+ self.log.exception(unicode(err))
+ raise
+ else:
+ self.log.error(unicode(err))
+ return 1
+ result = 1
+ if self.interactive_mode:
+ _argv = [sys.argv[0]]
+ sys.argv = _argv
+ result = self.interact()
+ else:
+ result = self.run_subcommand(remainder)
+ return result
+
+ def run_subcommand(self, argv):
+ subcommand = self.command_manager.find_command(argv)
+ cmd_factory, cmd_name, sub_argv = subcommand
+ cmd = cmd_factory(self, self.options)
+ err = None
+ result = 1
+ try:
+ self.prepare_to_run_command(cmd)
+ full_name = (cmd_name
+ if self.interactive_mode
+ else ' '.join([self.NAME, cmd_name])
+ )
+ cmd_parser = cmd.get_parser(full_name)
+ return run_command(cmd, cmd_parser, sub_argv)
+ except Exception as err:
+ if self.options.debug:
+ self.log.exception(unicode(err))
+ else:
+ self.log.error(unicode(err))
+ try:
+ self.clean_up(cmd, result, err)
+ except Exception as err2:
+ if self.options.debug:
+ self.log.exception(unicode(err2))
+ else:
+ self.log.error('Could not clean up: %s', unicode(err2))
+ if self.options.debug:
+ raise
+ else:
+ try:
+ self.clean_up(cmd, result, None)
+ except Exception as err3:
+ if self.options.debug:
+ self.log.exception(unicode(err3))
+ else:
+ self.log.error('Could not clean up: %s', unicode(err3))
+ return result
+
+ def authenticate_user(self):
+ """Make sure the user has provided all of the authentication
+ info we need.
+ """
+ if self.options.os_auth_strategy == 'keystone':
+ if self.options.os_token or self.options.os_url:
+ # Token flow auth takes priority
+ if not self.options.os_token:
+ raise exc.CommandError(
+ "You must provide a token via"
+ " either --os-token or env[OS_TOKEN]")
+
+ if not self.options.os_url:
+ raise exc.CommandError(
+ "You must provide a service URL via"
+ " either --os-url or env[OS_URL]")
+
+ else:
+ # Validate password flow auth
+ if not self.options.os_username:
+ raise exc.CommandError(
+ "You must provide a username via"
+ " either --os-username or env[OS_USERNAME]")
+
+ if not self.options.os_password:
+ raise exc.CommandError(
+ "You must provide a password via"
+ " either --os-password or env[OS_PASSWORD]")
+
+ if not (self.options.os_tenant_name):
+ raise exc.CommandError(
+ "You must provide a tenant_name via"
+ " either --os-tenant-name or via env[OS_TENANT_NAME]")
+
+ if not self.options.os_auth_url:
+ raise exc.CommandError(
+ "You must provide an auth url via"
+ " either --os-auth-url or via env[OS_AUTH_URL]")
+ else: # not keystone
+ if not self.options.os_url:
+ raise exc.CommandError(
+ "You must provide a service URL via"
+ " either --os-url or env[OS_URL]")
+
+ self.client_manager = clientmanager.ClientManager(
+ token=self.options.os_token,
+ url=self.options.os_url,
+ auth_url=self.options.os_auth_url,
+ tenant_name=self.options.os_tenant_name,
+ username=self.options.os_username,
+ password=self.options.os_password,
+ region_name=self.options.os_region_name,
+ api_version=self.api_version,
+ auth_strategy=self.options.os_auth_strategy,
+ endpoint_type=self.options.endpoint_type,
+ insecure=self.options.insecure, )
+ return
+
+ def initialize_app(self, argv):
+ """Global app init bits:
+
+ * set up API versions
+ * validate authentication info
+ """
+
+ super(NeutronShell, self).initialize_app(argv)
+
+ self.api_version = {'network': self.api_version}
+
+ # If the user is not asking for help, make sure they
+ # have given us auth.
+ cmd_name = None
+ if argv:
+ cmd_info = self.command_manager.find_command(argv)
+ cmd_factory, cmd_name, sub_argv = cmd_info
+ if self.interactive_mode or cmd_name != 'help':
+ self.authenticate_user()
+
+ def clean_up(self, cmd, result, err):
+ self.log.debug('clean_up %s', cmd.__class__.__name__)
+ if err:
+ self.log.debug('got an error: %s', unicode(err))
+
+ def configure_logging(self):
+ """Create logging handlers for any log output.
+ """
+ root_logger = logging.getLogger('')
+
+ # Set up logging to a file
+ root_logger.setLevel(logging.DEBUG)
+
+ # Send higher-level messages to the console via stderr
+ console = logging.StreamHandler(self.stderr)
+ console_level = {0: logging.WARNING,
+ 1: logging.INFO,
+ 2: logging.DEBUG,
+ }.get(self.options.verbose_level, logging.DEBUG)
+ console.setLevel(console_level)
+ if logging.DEBUG == console_level:
+ formatter = logging.Formatter(self.DEBUG_MESSAGE_FORMAT)
+ else:
+ formatter = logging.Formatter(self.CONSOLE_MESSAGE_FORMAT)
+ console.setFormatter(formatter)
+ root_logger.addHandler(console)
+ return
+
+
+def main(argv=sys.argv[1:]):
+ try:
+ return NeutronShell(NEUTRON_API_VERSION).run(map(strutils.safe_decode,
+ argv))
+ except exc.NeutronClientException:
+ return 1
+ except Exception as e:
+ print unicode(e)
+ return 1
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv[1:]))
diff --git a/neutronclient/tests/unit/test_utils.py b/neutronclient/tests/unit/test_utils.py
new file mode 100644
index 0000000..22c4ba4
--- /dev/null
+++ b/neutronclient/tests/unit/test_utils.py
@@ -0,0 +1,45 @@
+# Copyright 2013 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import testtools
+
+from neutronclient.common import utils
+
+
+class UtilsTest(testtools.TestCase):
+ def test_safe_encode_list(self):
+ o = object()
+ unicode_text = u'\u7f51\u7edc'
+ l = ['abc', unicode_text, unicode_text.encode('utf-8'), o]
+ expected = ['abc', unicode_text.encode('utf-8'),
+ unicode_text.encode('utf-8'), o]
+ self.assertEqual(utils.safe_encode_list(l), expected)
+
+ def test_safe_encode_dict(self):
+ o = object()
+ unicode_text = u'\u7f51\u7edc'
+ d = {'test1': unicode_text,
+ 'test2': [unicode_text, o],
+ 'test3': o,
+ 'test4': {'test5': unicode_text},
+ 'test6': unicode_text.encode('utf-8')}
+ expected = {'test1': unicode_text.encode('utf-8'),
+ 'test2': [unicode_text.encode('utf-8'), o],
+ 'test3': o,
+ 'test4': {'test5': unicode_text.encode('utf-8')},
+ 'test6': unicode_text.encode('utf-8')}
+ self.assertEqual(utils.safe_encode_dict(d), expected)
diff --git a/neutronclient/v2_0/__init__.py b/neutronclient/v2_0/__init__.py
new file mode 100644
index 0000000..63c3905
--- /dev/null
+++ b/neutronclient/v2_0/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py
new file mode 100644
index 0000000..9463614
--- /dev/null
+++ b/neutronclient/v2_0/client.py
@@ -0,0 +1,880 @@
+# Copyright 2012 OpenStack LLC.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import httplib
+import logging
+import time
+import urllib
+import urlparse
+
+from neutronclient import client
+from neutronclient.common import _
+from neutronclient.common import constants
+from neutronclient.common import exceptions
+from neutronclient.common import serializer
+from neutronclient.common import utils
+
+
+_logger = logging.getLogger(__name__)
+
+
+def exception_handler_v20(status_code, error_content):
+ """Exception handler for API v2.0 client
+
+ This routine generates the appropriate
+ Neutron exception according to the contents of the
+ response body
+
+ :param status_code: HTTP error status code
+ :param error_content: deserialized body of error response
+ """
+
+ neutron_errors = {
+ 'NetworkNotFound': exceptions.NetworkNotFoundClient,
+ 'NetworkInUse': exceptions.NetworkInUseClient,
+ 'PortNotFound': exceptions.PortNotFoundClient,
+ 'RequestedStateInvalid': exceptions.StateInvalidClient,
+ 'PortInUse': exceptions.PortInUseClient,
+ 'AlreadyAttached': exceptions.AlreadyAttachedClient, }
+
+ error_dict = None
+ if isinstance(error_content, dict):
+ error_dict = error_content.get('NeutronError')
+ # Find real error type
+ bad_neutron_error_flag = False
+ if error_dict:
+ # If Neutron key is found, it will definitely contain
+ # a 'message' and 'type' keys?
+ try:
+ error_type = error_dict['type']
+ error_message = (error_dict['message'] + "\n" +
+ error_dict['detail'])
+ except Exception:
+ bad_neutron_error_flag = True
+ if not bad_neutron_error_flag:
+ ex = None
+ try:
+ # raise the appropriate error!
+ ex = neutron_errors[error_type](message=error_message)
+ ex.args = ([dict(status_code=status_code,
+ message=error_message)], )
+ except Exception:
+ pass
+ if ex:
+ raise ex
+ else:
+ raise exceptions.NeutronClientException(status_code=status_code,
+ message=error_dict)
+ else:
+ message = None
+ if isinstance(error_content, dict):
+ message = error_content.get('message', None)
+ if message:
+ raise exceptions.NeutronClientException(status_code=status_code,
+ message=message)
+
+ # 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)
+
+
+class APIParamsCall(object):
+ """A Decorator to add support for format and tenant overriding
+ and filters
+ """
+ def __init__(self, function):
+ self.function = function
+
+ def __get__(self, instance, owner):
+ def with_params(*args, **kwargs):
+ _format = instance.format
+ if 'format' in kwargs:
+ instance.format = kwargs['format']
+ ret = self.function(instance, *args, **kwargs)
+ instance.format = _format
+ return ret
+ return with_params
+
+
+class Client(object):
+ """Client for the OpenStack Neutron v2.0 API.
+
+ :param string username: Username for authentication. (optional)
+ :param string password: Password for authentication. (optional)
+ :param string token: Token for authentication. (optional)
+ :param string tenant_name: Tenant name. (optional)
+ :param string auth_url: Keystone service endpoint for authorization.
+ :param string endpoint_type: Network service endpoint type to pull from the
+ keystone catalog (e.g. 'publicURL',
+ 'internalURL', or 'adminURL') (optional)
+ :param string region_name: Name of a region to select when choosing an
+ endpoint from the service catalog.
+ :param string endpoint_url: A user-supplied endpoint URL for the neutron
+ service. Lazy-authentication is possible for API
+ service calls if endpoint is set at
+ instantiation.(optional)
+ :param integer timeout: Allows customization of the timeout for client
+ http requests. (optional)
+ :param insecure: ssl certificate validation. (optional)
+
+ Example::
+
+ from neutronclient.v2_0 import client
+ neutron = client.Client(username=USER,
+ password=PASS,
+ tenant_name=TENANT_NAME,
+ auth_url=KEYSTONE_URL)
+
+ nets = neutron.list_networks()
+ ...
+
+ """
+
+ networks_path = "/networks"
+ network_path = "/networks/%s"
+ ports_path = "/ports"
+ port_path = "/ports/%s"
+ subnets_path = "/subnets"
+ subnet_path = "/subnets/%s"
+ quotas_path = "/quotas"
+ quota_path = "/quotas/%s"
+ extensions_path = "/extensions"
+ extension_path = "/extensions/%s"
+ routers_path = "/routers"
+ router_path = "/routers/%s"
+ floatingips_path = "/floatingips"
+ floatingip_path = "/floatingips/%s"
+ security_groups_path = "/security-groups"
+ security_group_path = "/security-groups/%s"
+ security_group_rules_path = "/security-group-rules"
+ security_group_rule_path = "/security-group-rules/%s"
+ vips_path = "/lb/vips"
+ vip_path = "/lb/vips/%s"
+ pools_path = "/lb/pools"
+ pool_path = "/lb/pools/%s"
+ pool_path_stats = "/lb/pools/%s/stats"
+ members_path = "/lb/members"
+ member_path = "/lb/members/%s"
+ health_monitors_path = "/lb/health_monitors"
+ health_monitor_path = "/lb/health_monitors/%s"
+ associate_pool_health_monitors_path = "/lb/pools/%s/health_monitors"
+ disassociate_pool_health_monitors_path = (
+ "/lb/pools/%(pool)s/health_monitors/%(health_monitor)s")
+ qos_queues_path = "/qos-queues"
+ qos_queue_path = "/qos-queues/%s"
+ agents_path = "/agents"
+ agent_path = "/agents/%s"
+ network_gateways_path = "/network-gateways"
+ network_gateway_path = "/network-gateways/%s"
+
+ DHCP_NETS = '/dhcp-networks'
+ DHCP_AGENTS = '/dhcp-agents'
+ L3_ROUTERS = '/l3-routers'
+ L3_AGENTS = '/l3-agents'
+ # API has no way to report plurals, so we have to hard code them
+ EXTED_PLURALS = {'routers': 'router',
+ 'floatingips': 'floatingip',
+ 'service_types': 'service_type',
+ 'service_definitions': 'service_definition',
+ 'security_groups': 'security_group',
+ 'security_group_rules': 'security_group_rule',
+ 'vips': 'vip',
+ 'pools': 'pool',
+ 'members': 'member',
+ 'health_monitors': 'health_monitor',
+ 'quotas': 'quota',
+ }
+ # 8192 Is the default max URI len for eventlet.wsgi.server
+ MAX_URI_LEN = 8192
+
+ def get_attr_metadata(self):
+ if self.format == 'json':
+ return {}
+ old_request_format = self.format
+ self.format = 'json'
+ exts = self.list_extensions()['extensions']
+ self.format = old_request_format
+ ns = dict([(ext['alias'], ext['namespace']) for ext in exts])
+ self.EXTED_PLURALS.update(constants.PLURALS)
+ return {'plurals': self.EXTED_PLURALS,
+ 'xmlns': constants.XML_NS_V20,
+ constants.EXT_NS: ns}
+
+ @APIParamsCall
+ def get_quotas_tenant(self, **_params):
+ """Fetch tenant info in server's context for
+ following quota operation.
+ """
+ return self.get(self.quota_path % 'tenant', params=_params)
+
+ @APIParamsCall
+ def list_quotas(self, **_params):
+ """Fetch all tenants' quotas."""
+ return self.get(self.quotas_path, params=_params)
+
+ @APIParamsCall
+ def show_quota(self, tenant_id, **_params):
+ """Fetch information of a certain tenant's quotas."""
+ return self.get(self.quota_path % (tenant_id), params=_params)
+
+ @APIParamsCall
+ def update_quota(self, tenant_id, body=None):
+ """Update a tenant's quotas."""
+ return self.put(self.quota_path % (tenant_id), body=body)
+
+ @APIParamsCall
+ def delete_quota(self, tenant_id):
+ """Delete the specified tenant's quota values."""
+ return self.delete(self.quota_path % (tenant_id))
+
+ @APIParamsCall
+ def list_extensions(self, **_params):
+ """Fetch a list of all exts 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."""
+ 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."""
+ # 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."""
+ return self.get(self.port_path % (port), params=_params)
+
+ @APIParamsCall
+ def create_port(self, body=None):
+ """Creates a new port."""
+ return self.post(self.ports_path, body=body)
+
+ @APIParamsCall
+ def update_port(self, port, body=None):
+ """Updates a port."""
+ return self.put(self.port_path % (port), body=body)
+
+ @APIParamsCall
+ def delete_port(self, port):
+ """Deletes the specified port."""
+ return self.delete(self.port_path % (port))
+
+ @APIParamsCall
+ def list_networks(self, retrieve_all=True, **_params):
+ """Fetches a list of all networks for a tenant."""
+ # Pass filters in "params" argument to do_request
+ return self.list('networks', self.networks_path, retrieve_all,
+ **_params)
+
+ @APIParamsCall
+ def show_network(self, network, **_params):
+ """Fetches information of a certain network."""
+ return self.get(self.network_path % (network), params=_params)
+
+ @APIParamsCall
+ def create_network(self, body=None):
+ """Creates a new network."""
+ return self.post(self.networks_path, body=body)
+
+ @APIParamsCall
+ def update_network(self, network, body=None):
+ """Updates a network."""
+ return self.put(self.network_path % (network), body=body)
+
+ @APIParamsCall
+ def delete_network(self, network):
+ """Deletes the specified network."""
+ return self.delete(self.network_path % (network))
+
+ @APIParamsCall
+ def list_subnets(self, retrieve_all=True, **_params):
+ """Fetches a list of all networks for a tenant."""
+ return self.list('subnets', self.subnets_path, retrieve_all,
+ **_params)
+
+ @APIParamsCall
+ def show_subnet(self, subnet, **_params):
+ """Fetches information of a certain subnet."""
+ return self.get(self.subnet_path % (subnet), params=_params)
+
+ @APIParamsCall
+ def create_subnet(self, body=None):
+ """Creates a new subnet."""
+ return self.post(self.subnets_path, body=body)
+
+ @APIParamsCall
+ def update_subnet(self, subnet, body=None):
+ """Updates a subnet."""
+ return self.put(self.subnet_path % (subnet), body=body)
+
+ @APIParamsCall
+ def delete_subnet(self, subnet):
+ """Deletes the specified subnet."""
+ return self.delete(self.subnet_path % (subnet))
+
+ @APIParamsCall
+ def list_routers(self, retrieve_all=True, **_params):
+ """Fetches a list of all routers for a tenant."""
+ # Pass filters in "params" argument to do_request
+ return self.list('routers', self.routers_path, retrieve_all,
+ **_params)
+
+ @APIParamsCall
+ def show_router(self, router, **_params):
+ """Fetches information of a certain router."""
+ return self.get(self.router_path % (router), params=_params)
+
+ @APIParamsCall
+ def create_router(self, body=None):
+ """Creates a new router."""
+ return self.post(self.routers_path, body=body)
+
+ @APIParamsCall
+ def update_router(self, router, body=None):
+ """Updates a router."""
+ return self.put(self.router_path % (router), body=body)
+
+ @APIParamsCall
+ def delete_router(self, router):
+ """Deletes the specified router."""
+ return self.delete(self.router_path % (router))
+
+ @APIParamsCall
+ def add_interface_router(self, router, body=None):
+ """Adds an internal network interface to the specified router."""
+ return self.put((self.router_path % router) + "/add_router_interface",
+ body=body)
+
+ @APIParamsCall
+ def remove_interface_router(self, router, body=None):
+ """Removes an internal network interface from the specified router."""
+ return self.put((self.router_path % router) +
+ "/remove_router_interface", body=body)
+
+ @APIParamsCall
+ def add_gateway_router(self, router, body=None):
+ """Adds an external network gateway to the specified router."""
+ return self.put((self.router_path % router),
+ body={'router': {'external_gateway_info': body}})
+
+ @APIParamsCall
+ def remove_gateway_router(self, router):
+ """Removes an external network gateway from the specified router."""
+ return self.put((self.router_path % router),
+ body={'router': {'external_gateway_info': {}}})
+
+ @APIParamsCall
+ def list_floatingips(self, retrieve_all=True, **_params):
+ """Fetches a list of all floatingips for a tenant."""
+ # Pass filters in "params" argument to do_request
+ return self.list('floatingips', self.floatingips_path, retrieve_all,
+ **_params)
+
+ @APIParamsCall
+ def show_floatingip(self, floatingip, **_params):
+ """Fetches information of a certain floatingip."""
+ return self.get(self.floatingip_path % (floatingip), params=_params)
+
+ @APIParamsCall
+ def create_floatingip(self, body=None):
+ """Creates a new floatingip."""
+ return self.post(self.floatingips_path, body=body)
+
+ @APIParamsCall
+ def update_floatingip(self, floatingip, body=None):
+ """Updates a floatingip."""
+ return self.put(self.floatingip_path % (floatingip), body=body)
+
+ @APIParamsCall
+ def delete_floatingip(self, floatingip):
+ """Deletes the specified floatingip."""
+ return self.delete(self.floatingip_path % (floatingip))
+
+ @APIParamsCall
+ def create_security_group(self, body=None):
+ """Creates a new security group."""
+ return self.post(self.security_groups_path, body=body)
+
+ @APIParamsCall
+ def update_security_group(self, security_group, body=None):
+ """Updates a security group."""
+ return self.put(self.security_group_path %
+ security_group, body=body)
+
+ @APIParamsCall
+ def list_security_groups(self, retrieve_all=True, **_params):
+ """Fetches a list of all security groups for a tenant."""
+ return self.list('security_groups', self.security_groups_path,
+ retrieve_all, **_params)
+
+ @APIParamsCall
+ def show_security_group(self, security_group, **_params):
+ """Fetches information of a certain security group."""
+ return self.get(self.security_group_path % (security_group),
+ params=_params)
+
+ @APIParamsCall
+ def delete_security_group(self, security_group):
+ """Deletes the specified security group."""
+ return self.delete(self.security_group_path % (security_group))
+
+ @APIParamsCall
+ def create_security_group_rule(self, body=None):
+ """Creates a new security group rule."""
+ return self.post(self.security_group_rules_path, body=body)
+
+ @APIParamsCall
+ def delete_security_group_rule(self, security_group_rule):
+ """Deletes the specified security group rule."""
+ return self.delete(self.security_group_rule_path %
+ (security_group_rule))
+
+ @APIParamsCall
+ def list_security_group_rules(self, retrieve_all=True, **_params):
+ """Fetches a list of all security group rules for a tenant."""
+ return self.list('security_group_rules',
+ self.security_group_rules_path,
+ retrieve_all, **_params)
+
+ @APIParamsCall
+ def show_security_group_rule(self, security_group_rule, **_params):
+ """Fetches information of a certain security group rule."""
+ return self.get(self.security_group_rule_path % (security_group_rule),
+ params=_params)
+
+ @APIParamsCall
+ def list_vips(self, retrieve_all=True, **_params):
+ """Fetches a list of all load balancer vips for a tenant."""
+ # Pass filters in "params" argument to do_request
+ return self.list('vips', self.vips_path, retrieve_all,
+ **_params)
+
+ @APIParamsCall
+ def show_vip(self, vip, **_params):
+ """Fetches information of a certain load balancer vip."""
+ return self.get(self.vip_path % (vip), params=_params)
+
+ @APIParamsCall
+ def create_vip(self, body=None):
+ """Creates a new load balancer vip."""
+ return self.post(self.vips_path, body=body)
+
+ @APIParamsCall
+ def update_vip(self, vip, body=None):
+ """Updates a load balancer vip."""
+ return self.put(self.vip_path % (vip), body=body)
+
+ @APIParamsCall
+ def delete_vip(self, vip):
+ """Deletes the specified load balancer vip."""
+ return self.delete(self.vip_path % (vip))
+
+ @APIParamsCall
+ def list_pools(self, retrieve_all=True, **_params):
+ """Fetches a list of all load balancer pools for a tenant."""
+ # Pass filters in "params" argument to do_request
+ return self.list('pools', self.pools_path, retrieve_all,
+ **_params)
+
+ @APIParamsCall
+ def show_pool(self, pool, **_params):
+ """Fetches information of a certain load balancer pool."""
+ return self.get(self.pool_path % (pool), params=_params)
+
+ @APIParamsCall
+ def create_pool(self, body=None):
+ """Creates a new load balancer pool."""
+ return self.post(self.pools_path, body=body)
+
+ @APIParamsCall
+ def update_pool(self, pool, body=None):
+ """Updates a load balancer pool."""
+ return self.put(self.pool_path % (pool), body=body)
+
+ @APIParamsCall
+ def delete_pool(self, pool):
+ """Deletes the specified load balancer pool."""
+ return self.delete(self.pool_path % (pool))
+
+ @APIParamsCall
+ def retrieve_pool_stats(self, pool, **_params):
+ """Retrieves stats for a certain load balancer pool."""
+ return self.get(self.pool_path_stats % (pool), params=_params)
+
+ @APIParamsCall
+ def list_members(self, retrieve_all=True, **_params):
+ """Fetches a list of all load balancer members for a tenant."""
+ # Pass filters in "params" argument to do_request
+ return self.list('members', self.members_path, retrieve_all,
+ **_params)
+
+ @APIParamsCall
+ def show_member(self, member, **_params):
+ """Fetches information of a certain load balancer member."""
+ return self.get(self.member_path % (member), params=_params)
+
+ @APIParamsCall
+ def create_member(self, body=None):
+ """Creates a new load balancer member."""
+ return self.post(self.members_path, body=body)
+
+ @APIParamsCall
+ def update_member(self, member, body=None):
+ """Updates a load balancer member."""
+ return self.put(self.member_path % (member), body=body)
+
+ @APIParamsCall
+ def delete_member(self, member):
+ """Deletes the specified load balancer member."""
+ return self.delete(self.member_path % (member))
+
+ @APIParamsCall
+ def list_health_monitors(self, retrieve_all=True, **_params):
+ """Fetches a list of all load balancer health monitors for a tenant."""
+ # Pass filters in "params" argument to do_request
+ return self.list('health_monitors', self.health_monitors_path,
+ retrieve_all, **_params)
+
+ @APIParamsCall
+ def show_health_monitor(self, health_monitor, **_params):
+ """Fetches information of a certain load balancer health monitor."""
+ return self.get(self.health_monitor_path % (health_monitor),
+ params=_params)
+
+ @APIParamsCall
+ def create_health_monitor(self, body=None):
+ """Creates a new load balancer health monitor."""
+ return self.post(self.health_monitors_path, body=body)
+
+ @APIParamsCall
+ def update_health_monitor(self, health_monitor, body=None):
+ """Updates a load balancer health monitor."""
+ return self.put(self.health_monitor_path % (health_monitor), body=body)
+
+ @APIParamsCall
+ def delete_health_monitor(self, health_monitor):
+ """Deletes the specified load balancer health monitor."""
+ return self.delete(self.health_monitor_path % (health_monitor))
+
+ @APIParamsCall
+ def associate_health_monitor(self, pool, body):
+ """Associate specified load balancer health monitor and pool."""
+ return self.post(self.associate_pool_health_monitors_path % (pool),
+ body=body)
+
+ @APIParamsCall
+ def disassociate_health_monitor(self, pool, health_monitor):
+ """Disassociate specified load balancer health monitor and pool."""
+ path = (self.disassociate_pool_health_monitors_path %
+ {'pool': pool, 'health_monitor': health_monitor})
+ return self.delete(path)
+
+ @APIParamsCall
+ def create_qos_queue(self, body=None):
+ """Creates a new queue."""
+ return self.post(self.qos_queues_path, body=body)
+
+ @APIParamsCall
+ def list_qos_queues(self, **_params):
+ """Fetches a list of all queues for a tenant."""
+ return self.get(self.qos_queues_path, params=_params)
+
+ @APIParamsCall
+ def show_qos_queue(self, queue, **_params):
+ """Fetches information of a certain queue."""
+ return self.get(self.qos_queue_path % (queue),
+ params=_params)
+
+ @APIParamsCall
+ def delete_qos_queue(self, queue):
+ """Deletes the specified queue."""
+ return self.delete(self.qos_queue_path % (queue))
+
+ @APIParamsCall
+ def list_agents(self, **_params):
+ """Fetches agents."""
+ # Pass filters in "params" argument to do_request
+ return self.get(self.agents_path, params=_params)
+
+ @APIParamsCall
+ def show_agent(self, agent, **_params):
+ """Fetches information of a certain agent."""
+ return self.get(self.agent_path % (agent), params=_params)
+
+ @APIParamsCall
+ def update_agent(self, agent, body=None):
+ """Updates an agent."""
+ return self.put(self.agent_path % (agent), body=body)
+
+ @APIParamsCall
+ def delete_agent(self, agent):
+ """Deletes the specified agent."""
+ return self.delete(self.agent_path % (agent))
+
+ @APIParamsCall
+ def list_network_gateways(self, **_params):
+ """Retrieve network gateways."""
+ return self.get(self.network_gateways_path, params=_params)
+
+ @APIParamsCall
+ def show_network_gateway(self, gateway_id, **_params):
+ """Fetch a network gateway."""
+ return self.get(self.network_gateway_path % gateway_id, params=_params)
+
+ @APIParamsCall
+ def create_network_gateway(self, body=None):
+ """Create a new network gateway."""
+ return self.post(self.network_gateways_path, body=body)
+
+ @APIParamsCall
+ def update_network_gateway(self, gateway_id, body=None):
+ """Update a network gateway."""
+ return self.put(self.network_gateway_path % gateway_id, body=body)
+
+ @APIParamsCall
+ def delete_network_gateway(self, gateway_id):
+ """Delete the specified network gateway."""
+ return self.delete(self.network_gateway_path % gateway_id)
+
+ @APIParamsCall
+ def connect_network_gateway(self, gateway_id, body=None):
+ """Connect a network gateway to the specified network."""
+ base_uri = self.network_gateway_path % gateway_id
+ return self.put("%s/connect_network" % base_uri, body=body)
+
+ @APIParamsCall
+ def disconnect_network_gateway(self, gateway_id, body=None):
+ """Disconnect a network from the specified gateway."""
+ base_uri = self.network_gateway_path % gateway_id
+ return self.put("%s/disconnect_network" % base_uri, body=body)
+
+ @APIParamsCall
+ def list_dhcp_agent_hosting_networks(self, network, **_params):
+ """Fetches a list of dhcp agents hosting a network."""
+ return self.get((self.network_path + self.DHCP_AGENTS) % network,
+ params=_params)
+
+ @APIParamsCall
+ def list_networks_on_dhcp_agent(self, dhcp_agent, **_params):
+ """Fetches a list of dhcp agents hosting a network."""
+ return self.get((self.agent_path + self.DHCP_NETS) % dhcp_agent,
+ params=_params)
+
+ @APIParamsCall
+ def add_network_to_dhcp_agent(self, dhcp_agent, body=None):
+ """Adds a network to dhcp agent."""
+ return self.post((self.agent_path + self.DHCP_NETS) % dhcp_agent,
+ body=body)
+
+ @APIParamsCall
+ def remove_network_from_dhcp_agent(self, dhcp_agent, network_id):
+ """Remove a network from dhcp agent."""
+ return self.delete((self.agent_path + self.DHCP_NETS + "/%s") % (
+ dhcp_agent, network_id))
+
+ @APIParamsCall
+ def list_l3_agent_hosting_routers(self, router, **_params):
+ """Fetches a list of L3 agents hosting a router."""
+ return self.get((self.router_path + self.L3_AGENTS) % router,
+ params=_params)
+
+ @APIParamsCall
+ def list_routers_on_l3_agent(self, l3_agent, **_params):
+ """Fetches a list of L3 agents hosting a router."""
+ return self.get((self.agent_path + self.L3_ROUTERS) % l3_agent,
+ params=_params)
+
+ @APIParamsCall
+ def add_router_to_l3_agent(self, l3_agent, body):
+ """Adds a router to L3 agent."""
+ return self.post((self.agent_path + self.L3_ROUTERS) % l3_agent,
+ body=body)
+
+ @APIParamsCall
+ def remove_router_from_l3_agent(self, l3_agent, router_id):
+ """Remove a router from l3 agent."""
+ return self.delete((self.agent_path + self.L3_ROUTERS + "/%s") % (
+ l3_agent, router_id))
+
+ def __init__(self, **kwargs):
+ """Initialize a new client for the Neutron v2.0 API."""
+ super(Client, self).__init__()
+ self.httpclient = client.HTTPClient(**kwargs)
+ self.version = '2.0'
+ self.format = 'json'
+ self.action_prefix = "/v%s" % (self.version)
+ self.retries = 0
+ self.retry_interval = 1
+
+ def _handle_fault_response(self, status_code, response_body):
+ # Create exception with HTTP status code and message
+ _logger.debug("Error message: %s", response_body)
+ # Add deserialized error message to exception arguments
+ try:
+ des_error_body = self.deserialize(response_body, status_code)
+ except Exception:
+ # If unable to deserialized body it is probably not a
+ # Neutron error
+ des_error_body = {'message': response_body}
+ # Raise the appropriate exception
+ exception_handler_v20(status_code, des_error_body)
+
+ def _check_uri_length(self, action):
+ uri_len = len(self.httpclient.endpoint_url) + len(action)
+ if uri_len > self.MAX_URI_LEN:
+ raise exceptions.RequestURITooLong(
+ excess=uri_len - self.MAX_URI_LEN)
+
+ def do_request(self, method, action, body=None, headers=None, params=None):
+ # Add format and tenant_id
+ action += ".%s" % self.format
+ action = self.action_prefix + action
+ if type(params) is dict and params:
+ params = utils.safe_encode_dict(params)
+ action += '?' + urllib.urlencode(params, doseq=1)
+ # Ensure client always has correct uri - do not guesstimate anything
+ self.httpclient.authenticate_and_fetch_endpoint_url()
+ self._check_uri_length(action)
+
+ if body:
+ body = self.serialize(body)
+ self.httpclient.content_type = self.content_type()
+ resp, replybody = self.httpclient.do_request(action, method, body=body)
+ status_code = self.get_status_code(resp)
+ if status_code in (httplib.OK,
+ httplib.CREATED,
+ httplib.ACCEPTED,
+ httplib.NO_CONTENT):
+ return self.deserialize(replybody, status_code)
+ else:
+ self._handle_fault_response(status_code, replybody)
+
+ def get_auth_info(self):
+ return self.httpclient.get_auth_info()
+
+ def get_status_code(self, response):
+ """Returns the integer status code from the response.
+
+ Either a Webob.Response (used in testing) or httplib.Response
+ is returned.
+ """
+ if hasattr(response, 'status_int'):
+ return response.status_int
+ else:
+ return response.status
+
+ def serialize(self, data):
+ """Serializes a dictionary into either xml or json.
+
+ A dictionary with a single key can be passed and
+ it can contain any structure.
+ """
+ if data is None:
+ return None
+ elif type(data) is dict:
+ return serializer.Serializer(
+ self.get_attr_metadata()).serialize(data, self.content_type())
+ else:
+ raise Exception("unable to serialize object of type = '%s'" %
+ type(data))
+
+ def deserialize(self, data, status_code):
+ """Deserializes an xml or json string into a dictionary."""
+ if status_code == 204:
+ return data
+ return serializer.Serializer(self.get_attr_metadata()).deserialize(
+ data, self.content_type())['body']
+
+ def content_type(self, _format=None):
+ """Returns the mime-type for either 'xml' or 'json'.
+
+ Defaults to the currently set format.
+ """
+ _format = _format or self.format
+ return "application/%s" % (_format)
+
+ def retry_request(self, method, action, body=None,
+ headers=None, params=None):
+ """Call do_request with the default retry configuration.
+
+ Only idempotent requests should retry failed connection attempts.
+ :raises: ConnectionFailed if the maximum # of retries is exceeded
+ """
+ max_attempts = self.retries + 1
+ for i in xrange(max_attempts):
+ try:
+ return self.do_request(method, action, body=body,
+ headers=headers, params=params)
+ except exceptions.ConnectionFailed:
+ # Exception has already been logged by do_request()
+ if i < self.retries:
+ _logger.debug(_('Retrying connection to Neutron service'))
+ time.sleep(self.retry_interval)
+
+ raise exceptions.ConnectionFailed(reason=_("Maximum attempts reached"))
+
+ def delete(self, action, body=None, headers=None, params=None):
+ return self.retry_request("DELETE", action, body=body,
+ headers=headers, params=params)
+
+ def get(self, action, body=None, headers=None, params=None):
+ return self.retry_request("GET", action, body=body,
+ headers=headers, params=params)
+
+ def post(self, action, body=None, headers=None, params=None):
+ # Do not retry POST requests to avoid the orphan objects problem.
+ return self.do_request("POST", action, body=body,
+ headers=headers, params=params)
+
+ def put(self, action, body=None, headers=None, params=None):
+ return self.retry_request("PUT", action, body=body,
+ headers=headers, params=params)
+
+ def list(self, collection, path, retrieve_all=True, **params):
+ if retrieve_all:
+ res = []
+ for r in self._pagination(collection, path, **params):
+ res.extend(r[collection])
+ return {collection: res}
+ else:
+ return self._pagination(collection, path, **params)
+
+ def _pagination(self, collection, path, **params):
+ if params.get('page_reverse', False):
+ linkrel = 'previous'
+ else:
+ linkrel = 'next'
+ next = True
+ while next:
+ res = self.get(path, params=params)
+ yield res
+ next = False
+ try:
+ for link in res['%s_links' % collection]:
+ if link['rel'] == linkrel:
+ query_str = urlparse.urlparse(link['href']).query
+ params = urlparse.parse_qs(query_str)
+ next = True
+ break
+ except KeyError:
+ break
diff --git a/neutronclient/version.py b/neutronclient/version.py
new file mode 100644
index 0000000..9315671
--- /dev/null
+++ b/neutronclient/version.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+# 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.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# @author: Carl Baldwin, Hewlett-Packard
+
+import pbr.version
+
+
+__version__ = pbr.version.VersionInfo('python-neutronclient').version_string()